diff --git a/hapi/hedera-protobufs/services/auxiliary/tss/tss_share_signature.proto b/hapi/hedera-protobufs/services/auxiliary/tss/tss_share_signature.proto deleted file mode 100644 index 8ff4b14f1989..000000000000 --- a/hapi/hedera-protobufs/services/auxiliary/tss/tss_share_signature.proto +++ /dev/null @@ -1,88 +0,0 @@ -/** - * # Tss Share Signature - * Represents a transaction that submits a node's share signature on a block hash - * during the TSS (Threshold Signature Scheme) process. - * - * ### 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.services.auxiliary.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.services.auxiliary.tss.legacy"; -// <<>> This comment is special code for setting PBJ Compiler java package -option java_multiple_files = true; - -/** - * A TSS Share Signature transaction Body.
- * This transaction body communicates a node's signature of a block hash - * using its private share within the TSS process. - * This transaction MUST be prioritized for low latency gossip transmission. - * - * ### Block Stream Effects - * This transaction body will be present in the block stream. This will not have - * any state changes or transaction output or transaction result. - */ -message TssShareSignatureTransactionBody { - /** - * A SHA2-384 Hash.
- * This is the hash of the roster that includes the node whose - * share produced this share signature. - *

- * This value is REQUIRED.
- * This value MUST identify the network roster active at the time this - * share signature was produced.
- * This share signature MUST be produced from a share distributed during - * the re-keying process for the identified roster. - */ - bytes roster_hash = 1; - - /** - * An index of the share from the node private shares.
- * This is the index of the share that produced this share signature. - *

- * This value is REQUIRED.
- * The share referred to by this index MUST exist.
- * The share index MUST be greater than or equal to 0. - */ - uint64 share_index = 2; - - /** - * A SHA2-384 hash.
- * This is the hash of the message that was signed. - *

- * This value is REQUIRED.
- * The message signed MUST be a block hash. - */ - bytes message_hash = 3; - - /** - * The signature bytes.
- * This is the signature generated by signing the block hash with the node's private share. - *

- * This value is REQUIRED.
- * This value MUST be a valid signature of the message hash with the node's private share. - */ - bytes share_signature = 4; -} diff --git a/hapi/hedera-protobufs/services/basic_types.proto b/hapi/hedera-protobufs/services/basic_types.proto index 7138279ce8a9..298fa5245eb3 100644 --- a/hapi/hedera-protobufs/services/basic_types.proto +++ b/hapi/hedera-protobufs/services/basic_types.proto @@ -1193,6 +1193,10 @@ message SignatureMap { * The transactions and queries supported by Hedera Hashgraph. */ enum HederaFunctionality { + // FUTURE - Uncomment when https://github.com/hashgraph/pbj/issues/339 is fixed; + // currently the PBJ-generated unit tests fail when using reserved ordinals + // reserved 96, 97, 98, 99; + /** * Unused - The first value is unused because this default value is * ambiguous with an "unset" value and therefore should not be used. @@ -1644,26 +1648,6 @@ enum HederaFunctionality { */ TokenClaimAirdrop = 95; - /** - * A message produced as part of Threshold Signature Scheme (TSS) processing. - */ - TssMessage = 96; - - /** - * Submit a vote as part of the Threshold Signature Scheme (TSS) processing. - */ - TssVote = 97; - - /** - * Submit a node signature as part of the Threshold Signature Scheme (TSS) processing. - */ - TssShareSignature = 98; - - /** - * Submit a node public tss encryption key as part of the Threshold Signature Scheme (TSS). - */ - TssEncryptionKey = 99; - /** * Submit a signature of a state root hash gossiped to other nodes */ diff --git a/hapi/hedera-protobufs/services/transaction_body.proto b/hapi/hedera-protobufs/services/transaction_body.proto index f4036ccbdfaa..b41e58563849 100644 --- a/hapi/hedera-protobufs/services/transaction_body.proto +++ b/hapi/hedera-protobufs/services/transaction_body.proto @@ -103,11 +103,6 @@ import "node_create.proto"; import "node_update.proto"; import "node_delete.proto"; -import "auxiliary/tss/tss_message.proto"; -import "auxiliary/tss/tss_vote.proto"; -import "auxiliary/tss/tss_share_signature.proto"; -import "auxiliary/tss/tss_encryption_key.proto"; - import "event/state_signature_transaction.proto"; /** @@ -124,7 +119,9 @@ import "event/state_signature_transaction.proto"; * various parameters required to process a transaction. */ message TransactionBody { - reserved 30; // removed prior to earliest available history. + reserved 30, 61, 62, 63, 64; + + reserved "tssMessage", "tssVote", "tssShareSignature", "tssEncryptionKey"; /** * A transaction identifier.
@@ -559,26 +556,6 @@ message TransactionBody { */ TokenClaimAirdropTransactionBody tokenClaimAirdrop = 60; - /** - * A transaction body for a `tssMessage` request. - */ - com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody tssMessage = 61; - - /** - * A transaction body for a `tssVote` request. - */ - com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody tssVote = 62; - - /** - * A transaction body for node signature as part of the Threshold Signature Scheme (TSS) processing. - */ - com.hedera.hapi.services.auxiliary.tss.TssShareSignatureTransactionBody tssShareSignature = 63; - - /** - * A transaction body for a 'tssEncryptionKey` request - */ - com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody tssEncryptionKey = 64; - /** * A transaction body for signature of a state root hash gossiped to other nodes */ diff --git a/hapi/internal-protobufs/network/network.proto b/hapi/internal-protobufs/network/network.proto index 786b035c964c..f2403713d112 100644 --- a/hapi/internal-protobufs/network/network.proto +++ b/hapi/internal-protobufs/network/network.proto @@ -35,7 +35,6 @@ package com.hedera.node.internal.network; * limitations under the License. */ -import "auxiliary/tss/tss_message.proto"; import "state/addressbook/node.proto"; import "state/roster/roster.proto"; @@ -65,32 +64,16 @@ option java_multiple_files = true; * resets. */ message Network { + reserved 2; + /** * The metadata of the nodes in the network. */ repeated NodeMetadata node_metadata = 1; /** - * A list of TSS messages that have been generated by the nodes in - * a target network that is receiving a "transplant state".
- * This gives the capability to preserve the ledger id of the target network - * even if the transplant state has no keys or roster entries in common with - * the target. - *

- * This MUST be exactly the ordered list of TSS messages used to generate - * the target network's ledger id; and must generate the same ledger id - * given below. - */ - repeated com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody tss_messages = 2; - - /** - * A public key.
- * This key both identifies the ledger and can be used to verify ledger - * signatures on a block root hash. - *

- * This value MUST be set.
- * This value MUST NOT be empty.
- * This value MUST contain a valid public key. + * If set, the hash of the first roster that adopted TSS.
+ * A public input to every hinTS verification key proof. */ bytes ledger_id = 3; } @@ -99,6 +82,8 @@ message Network { * The full information needed for a single node in the network state. */ message NodeMetadata { + reserved 3; + /** * The node's entry in the current roster at the time of the network snapshot; * required to validate the network's ledger id if set. @@ -114,12 +99,4 @@ message NodeMetadata { * in the network state.
*/ com.hedera.hapi.node.state.addressbook.Node node = 2; - - /** - * An elliptic curve public encryption key.
- * If set, the public part of a prescribed TSS encryption key for the node. - * This is currently a BN254 elliptic curve, but this type may be subject to - * change in the future. - */ - bytes tss_encryption_key = 3; } 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 d239ca16cd65..bb67fb0980e1 100644 --- a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java +++ b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -232,10 +232,6 @@ public static HederaFunctionality functionOf(final TransactionBody txn) throws U case TOKEN_AIRDROP -> HederaFunctionality.TOKEN_AIRDROP; case TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP; case TOKEN_CLAIM_AIRDROP -> HederaFunctionality.TOKEN_CLAIM_AIRDROP; - case TSS_MESSAGE -> HederaFunctionality.TSS_MESSAGE; - case TSS_VOTE -> HederaFunctionality.TSS_VOTE; - case TSS_SHARE_SIGNATURE -> HederaFunctionality.TSS_SHARE_SIGNATURE; - case TSS_ENCRYPTION_KEY -> HederaFunctionality.TSS_ENCRYPTION_KEY; case STATE_SIGNATURE_TRANSACTION -> HederaFunctionality.STATE_SIGNATURE_TRANSACTION; case UNSET -> throw new UnknownHederaFunctionality(); }; diff --git a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/BlockStreamManagerBenchmark.java b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/BlockStreamManagerBenchmark.java index 471acf95e398..216b46dd95ac 100644 --- a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/BlockStreamManagerBenchmark.java +++ b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/BlockStreamManagerBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -20,9 +20,8 @@ import static com.hedera.hapi.block.stream.output.StateIdentifier.STATE_ID_BLOCK_STREAM_INFO; import static com.hedera.hapi.block.stream.output.StateIdentifier.STATE_ID_PLATFORM_STATE; import static com.hedera.node.app.blocks.BlockStreamManager.ZERO_BLOCK_HASH; +import static com.hedera.node.app.blocks.MockBlockHashSigner.MOCK_BLOCK_HASH_SIGNER; import static com.hedera.node.app.blocks.schemas.V0560BlockStreamSchema.BLOCK_STREAM_INFO_KEY; -import static com.hedera.node.app.spi.AppContext.Gossip.UNAVAILABLE_GOSSIP; -import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -43,19 +42,13 @@ import com.hedera.node.app.blocks.schemas.V0560BlockStreamSchema; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.fixtures.state.FakeState; -import com.hedera.node.app.services.AppContextImpl; -import com.hedera.node.app.spi.AppContext; import com.hedera.node.app.spi.signatures.SignatureVerifier; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssLibraryImpl; import com.hedera.node.config.ConfigProvider; import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.ParseException; import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Hash; -import com.swirlds.common.metrics.noop.NoOpMetrics; -import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.system.Round; @@ -105,7 +98,6 @@ public class BlockStreamManagerBenchmark { private static final String SAMPLE_BLOCK = "sample.blk.gz"; private static final Instant FAKE_CONSENSUS_NOW = Instant.ofEpochSecond(1_234_567L, 890); private static final Timestamp FAKE_CONSENSUS_TIME = new Timestamp(1_234_567L, 890); - private static final Metrics NO_OP_METRICS = new NoOpMetrics(); private static final SemanticVersion VERSION = new SemanticVersion(0, 56, 0, "", ""); public static void main(String... args) throws Exception { @@ -120,29 +112,12 @@ public static void main(String... args) throws Exception { "blockStream.hashCombineBatchSize", "64", "blockStream.serializationBatchSize", "32")); private final List roundItems = new ArrayList<>(); - final AppContext appContext = new AppContextImpl( - Instant::now, - fakeSignatureVerifier(), - UNAVAILABLE_GOSSIP, - configProvider::getConfiguration, - () -> DEFAULT_NODE_INFO, - () -> NO_OP_METRICS, - (split, snapshots) -> { - throw new UnsupportedOperationException(); - }); - private final TssBaseServiceImpl tssBaseService = new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), - ForkJoinPool.commonPool(), - new NoOpMetrics()); private final BlockStreamManagerImpl subject = new BlockStreamManagerImpl( + MOCK_BLOCK_HASH_SIGNER, NoopBlockItemWriter::new, // BaosBlockItemWriter::new, ForkJoinPool.commonPool(), configProvider, - tssBaseService, new FakeBoundaryStateChangeListener(), new InitialStateHash(completedFuture(FAKE_START_OF_BLOCK_STATE_HASH), FIRST_ROUND_NO - 1), VERSION); @@ -165,7 +140,6 @@ public void setup() throws IOException, ParseException { addServiceSingleton(new V0560BlockStreamSchema(ignore -> {}), BlockStreamService.NAME, BlockStreamInfo.DEFAULT); addServiceSingleton(new V0540PlatformStateSchema(), PlatformStateService.NAME, platformState); subject.initLastBlockHash(ZERO_BLOCK_HASH); - tssBaseService.registerLedgerSignatureConsumer(subject); } @Benchmark diff --git a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/MockBlockHashSigner.java b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/MockBlockHashSigner.java new file mode 100644 index 000000000000..27b5fbe6b747 --- /dev/null +++ b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/MockBlockHashSigner.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024-2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.blocks; + +import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.CompletableFuture; + +/** + * A mock implementation of the {@link BlockHashSigner} that "signs" block hashes by + * scheduling their SHA-384 hash. + */ +public enum MockBlockHashSigner implements BlockHashSigner { + MOCK_BLOCK_HASH_SIGNER; + + @Override + public boolean isReady() { + return true; + } + + @Override + public CompletableFuture signFuture(@NonNull final Bytes blockHash) { + requireNonNull(blockHash); + return CompletableFuture.supplyAsync(() -> noThrowSha384HashOf(blockHash)); + } +} diff --git a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/StandaloneRoundManagement.java b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/StandaloneRoundManagement.java index a7b0e0fddb3e..d3defeb27eca 100644 --- a/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/StandaloneRoundManagement.java +++ b/hedera-node/hedera-app/src/jmh/java/com/hedera/node/app/blocks/StandaloneRoundManagement.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -20,9 +20,8 @@ import static com.hedera.hapi.block.stream.output.StateIdentifier.STATE_ID_BLOCK_STREAM_INFO; import static com.hedera.hapi.block.stream.output.StateIdentifier.STATE_ID_PLATFORM_STATE; import static com.hedera.node.app.blocks.BlockStreamManager.ZERO_BLOCK_HASH; +import static com.hedera.node.app.blocks.MockBlockHashSigner.MOCK_BLOCK_HASH_SIGNER; import static com.hedera.node.app.blocks.schemas.V0560BlockStreamSchema.BLOCK_STREAM_INFO_KEY; -import static com.hedera.node.app.spi.AppContext.Gossip.UNAVAILABLE_GOSSIP; -import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -43,11 +42,7 @@ import com.hedera.node.app.blocks.schemas.V0560BlockStreamSchema; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.fixtures.state.FakeState; -import com.hedera.node.app.services.AppContextImpl; -import com.hedera.node.app.spi.AppContext; import com.hedera.node.app.spi.signatures.SignatureVerifier; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssLibraryImpl; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.BlockStreamConfig; import com.hedera.pbj.runtime.OneOf; @@ -55,8 +50,6 @@ import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Hash; -import com.swirlds.common.metrics.noop.NoOpMetrics; -import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.system.Round; @@ -85,7 +78,6 @@ public class StandaloneRoundManagement { private static final String SAMPLE_BLOCK = "sample.blk.gz"; private static final Instant FAKE_CONSENSUS_NOW = Instant.ofEpochSecond(1_234_567L, 890); private static final Timestamp FAKE_CONSENSUS_TIME = new Timestamp(1_234_567L, 890); - private static final Metrics NO_OP_METRICS = new NoOpMetrics(); private static final SemanticVersion VERSION = new SemanticVersion(0, 56, 0, "", ""); private static final int NUM_ROUNDS = 10000; @@ -96,29 +88,11 @@ public class StandaloneRoundManagement { private final ConfigProvider configProvider = new ConfigProviderImpl(false, null, Map.of("blockStream.serializationBatchSize", "32")); private final List roundItems = new ArrayList<>(); - final AppContext appContext = new AppContextImpl( - Instant::now, - fakeSignatureVerifier(), - UNAVAILABLE_GOSSIP, - configProvider::getConfiguration, - () -> DEFAULT_NODE_INFO, - () -> NO_OP_METRICS, - (split, snapshots) -> { - throw new UnsupportedOperationException(); - }); - - private final TssBaseServiceImpl tssBaseService = new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), - ForkJoinPool.commonPool(), - new NoOpMetrics()); private final BlockStreamManagerImpl subject = new BlockStreamManagerImpl( + MOCK_BLOCK_HASH_SIGNER, NoopBlockItemWriter::new, ForkJoinPool.commonPool(), configProvider, - tssBaseService, new FakeBoundaryStateChangeListener(), new InitialStateHash(completedFuture(FAKE_START_OF_BLOCK_STATE_HASH), FIRST_ROUND_NO - 1), VERSION); @@ -142,7 +116,6 @@ public void setup() throws IOException, ParseException { addServiceSingleton(new V0560BlockStreamSchema(ignore -> {}), BlockStreamService.NAME, BlockStreamInfo.DEFAULT); addServiceSingleton(new V0540PlatformStateSchema(), PlatformStateService.NAME, platformState); subject.initLastBlockHash(ZERO_BLOCK_HASH); - tssBaseService.registerLedgerSignatureConsumer(subject); System.out.println("serializationBatchSize = " + configProvider .getConfiguration() 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 0adf358ddf9b..2480f177653d 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -60,6 +60,7 @@ import com.hedera.hapi.platform.state.PlatformState; import com.hedera.hapi.util.HapiUtils; import com.hedera.hapi.util.UnknownHederaFunctionality; +import com.hedera.node.app.blocks.BlockHashSigner; import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.BlockStreamService; import com.hedera.node.app.blocks.InitialStateHash; @@ -101,7 +102,7 @@ import com.hedera.node.app.throttle.AppThrottleFactory; import com.hedera.node.app.throttle.CongestionThrottleService; import com.hedera.node.app.throttle.ThrottleAccumulator; -import com.hedera.node.app.tss.TssBaseService; +import com.hedera.node.app.tss.TssBaseServiceImpl; import com.hedera.node.app.version.ServicesSoftwareVersion; import com.hedera.node.app.workflows.handle.HandleWorkflow; import com.hedera.node.app.workflows.ingest.IngestWorkflow; @@ -249,12 +250,6 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener, A */ private final ScheduleServiceImpl scheduleServiceImpl; - /** - * The TSS base service singleton, kept as a field here to avoid constructing twice - * (once in constructor to register schemas, again inside Dagger component). - */ - private final TssBaseService tssBaseService; - /** * The file service singleton, kept as a field here to avoid constructing twice * (once in constructor to register schemas, again inside Dagger component). @@ -267,6 +262,11 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener, A */ private final BlockStreamService blockStreamService; + /** + * The block hash signer factory. + */ + private final BlockHashSignerFactory blockHashSignerFactory; + /** * The bootstrap configuration provider for the network. */ @@ -353,15 +353,15 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener, A private StartupNetworks startupNetworks; @FunctionalInterface - public interface TssBaseServiceFactory { + public interface StartupNetworksFactory { @NonNull - TssBaseService apply(@NonNull AppContext appContext); + StartupNetworks apply(@NonNull ConfigProvider configProvider); } @FunctionalInterface - public interface StartupNetworksFactory { + public interface BlockHashSignerFactory { @NonNull - StartupNetworks apply(@NonNull ConfigProvider configProvider, @NonNull TssBaseService tssBaseService); + BlockHashSigner apply(); } /*================================================================================================================== @@ -380,20 +380,21 @@ public interface StartupNetworksFactory { * @param constructableRegistry the registry to register {@link RuntimeConstructable} factories with * @param registryFactory the factory to use for creating the services registry * @param migrator the migrator to use with the services - * @param tssBaseServiceFactory the factory for the TSS base service * @param startupNetworksFactory the factory for the startup networks + * @param blockHashSignerFactory the factory for the block hash signer */ public Hedera( @NonNull final ConstructableRegistry constructableRegistry, @NonNull final ServicesRegistry.Factory registryFactory, @NonNull final ServiceMigrator migrator, @NonNull final InstantSource instantSource, - @NonNull final TssBaseServiceFactory tssBaseServiceFactory, - @NonNull final StartupNetworksFactory startupNetworksFactory) { + @NonNull final StartupNetworksFactory startupNetworksFactory, + @NonNull final BlockHashSignerFactory blockHashSignerFactory) { requireNonNull(registryFactory); requireNonNull(constructableRegistry); this.serviceMigrator = requireNonNull(migrator); this.startupNetworksFactory = requireNonNull(startupNetworksFactory); + this.blockHashSignerFactory = requireNonNull(blockHashSignerFactory); logger.info( """ @@ -432,7 +433,6 @@ public Hedera( () -> daggerApp.workingStateAccessor().getState(), () -> daggerApp.throttleServiceManager().activeThrottleDefinitionsOrThrow(), ThrottleAccumulator::new)); - tssBaseService = tssBaseServiceFactory.apply(appContext); contractServiceImpl = new ContractServiceImpl(appContext); scheduleServiceImpl = new ScheduleServiceImpl(); blockStreamService = new BlockStreamService(); @@ -442,7 +442,7 @@ public Hedera( new ConsensusServiceImpl(), contractServiceImpl, fileServiceImpl, - tssBaseService, + new TssBaseServiceImpl(), new FreezeServiceImpl(), scheduleServiceImpl, new TokenServiceImpl(), @@ -646,7 +646,7 @@ private void migrateSchemas( genesisNetworkInfo = new GenesisNetworkInfo(requireNonNull(genesisNetwork), ledgerConfig.id()); } blockStreamService.resetMigratedLastBlockHash(); - startupNetworks = startupNetworksFactory.apply(configProvider, tssBaseService); + startupNetworks = startupNetworksFactory.apply(configProvider); 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 @@ -985,7 +985,6 @@ private void initializeDagger(@NonNull final State state, @NonNull final InitTri notifications.unregister(StateWriteToDiskCompleteListener.class, daggerApp.stateWriteToDiskListener()); if (blockStreamEnabled) { notifications.unregister(StateHashedListener.class, daggerApp.blockStreamManager()); - daggerApp.tssBaseService().unregisterLedgerSignatureConsumer(daggerApp.blockStreamManager()); } } if (trigger == RECONNECT) { @@ -1003,9 +1002,8 @@ private void initializeDagger(@NonNull final State state, @NonNull final InitTri .round(); final var initialStateHash = new InitialStateHash(initialStateHashFuture, roundNum); - final var activeRoster = tssBaseService.chooseRosterForNetwork( - state, trigger, serviceMigrator, version, configProvider.getConfiguration(), platform.getRoster()); - final var networkInfo = new StateNetworkInfo(platform.getSelfId().id(), state, activeRoster, configProvider); + final var networkInfo = + new StateNetworkInfo(platform.getSelfId().id(), state, platform.getRoster(), configProvider); // Fully qualified so as to not confuse javadoc daggerApp = com.hedera.node.app.DaggerHederaInjectionComponent.builder() .configProviderImpl(configProvider) @@ -1013,7 +1011,6 @@ private void initializeDagger(@NonNull final State state, @NonNull final InitTri .fileServiceImpl(fileServiceImpl) .contractServiceImpl(contractServiceImpl) .scheduleService(scheduleServiceImpl) - .tssBaseService(tssBaseService) .initTrigger(trigger) .softwareVersion(version.getPbjSemanticVersion()) .self(networkInfo.selfNodeInfo()) @@ -1031,6 +1028,8 @@ private void initializeDagger(@NonNull final State state, @NonNull final InitTri .initialStateHash(initialStateHash) .networkInfo(networkInfo) .startupNetworks(startupNetworks) + // (FUTURE) Pass the HintsService and HistoryService to this factory + .blockHashSigner(blockHashSignerFactory.apply()) .build(); // Initialize infrastructure for fees, exchange rates, and throttles from the working state daggerApp.initializer().accept(state); @@ -1048,7 +1047,6 @@ private void initializeDagger(@NonNull final State state, @NonNull final InitTri .migratedLastBlockHash() .orElseGet(() -> startBlockHashFrom(state)); }); - daggerApp.tssBaseService().registerLedgerSignatureConsumer(daggerApp.blockStreamManager()); migrationStateChanges = null; } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java index 2de952d208e3..ece99d0dda4e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC + * Copyright (C) 2021-2025 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. @@ -20,6 +20,7 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.node.app.annotations.MaxSignedTxnSize; import com.hedera.node.app.authorization.AuthorizerInjectionModule; +import com.hedera.node.app.blocks.BlockHashSigner; import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.BlockStreamModule; import com.hedera.node.app.blocks.InitialStateHash; @@ -50,7 +51,6 @@ import com.hedera.node.app.state.WorkingStateAccessor; import com.hedera.node.app.throttle.ThrottleServiceManager; import com.hedera.node.app.throttle.ThrottleServiceModule; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.workflows.FacilityInitModule; import com.hedera.node.app.workflows.WorkflowsInjectionModule; import com.hedera.node.app.workflows.handle.HandleWorkflow; @@ -144,8 +144,6 @@ public interface HederaInjectionComponent { StoreMetricsService storeMetricsService(); - TssBaseService tssBaseService(); - SubmissionManager submissionManager(); @Component.Builder @@ -186,6 +184,9 @@ interface Builder { @BindsInstance Builder currentPlatformStatus(CurrentPlatformStatus currentPlatformStatus); + @BindsInstance + Builder blockHashSigner(BlockHashSigner blockHashSigner); + @BindsInstance Builder instantSource(InstantSource instantSource); @@ -207,9 +208,6 @@ interface Builder { @BindsInstance Builder migrationStateChanges(List migrationStateChanges); - @BindsInstance - Builder tssBaseService(TssBaseService tssBaseService); - @BindsInstance Builder initialStateHash(InitialStateHash initialStateHash); 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 5f21e3cac841..352c05305e85 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -46,8 +46,7 @@ import com.hedera.node.app.services.OrderedServiceMigrator; import com.hedera.node.app.services.ServicesRegistryImpl; import com.hedera.node.app.store.ReadableStoreFactory; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssLibraryImpl; +import com.hedera.node.app.tss.TssBlockHashSigner; import com.swirlds.base.time.Time; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.RuntimeConstructable; @@ -95,7 +94,6 @@ import java.time.InstantSource; import java.util.List; 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; @@ -366,14 +364,8 @@ public static Hedera newHedera(@NonNull final NodeId selfNodeId, @NonNull final ServicesRegistryImpl::new, new OrderedServiceMigrator(), InstantSource.system(), - appContext -> new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), - ForkJoinPool.commonPool(), - metrics), - DiskStartupNetworks::new); + DiskStartupNetworks::new, + TssBlockHashSigner::new); } /** 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/blocks/BlockHashSigner.java similarity index 55% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssStatus.java rename to hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockHashSigner.java index 25e6aa686f06..a12d75274877 100644 --- 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/blocks/BlockHashSigner.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -14,13 +14,26 @@ * limitations under the License. */ -package com.hedera.node.app.tss; +package com.hedera.node.app.blocks; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.CompletableFuture; /** - * 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. + * Provides the ability to asynchronously sign a block hash. */ -public record TssStatus(TssKeyingStatus tssKeyingStatus, RosterToKey rosterToKey, @NonNull Bytes ledgerId) {} +public interface BlockHashSigner { + /** + * Whether the signer is ready. + */ + boolean isReady(); + + /** + * Returns a future that resolves to the signature of the given block hash. + * + * @param blockHash the block hash + * @return the future + */ + CompletableFuture signFuture(@NonNull Bytes blockHash); +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java index c421e0d9392c..366c5fc683a8 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -24,7 +24,6 @@ import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; -import java.util.function.BiConsumer; /** * Maintains the state and process objects needed to produce the block stream. @@ -37,7 +36,7 @@ * Items written to the stream will be produced in the order they are written. The leaves of the input and output item * Merkle trees will be in the order they are written. */ -public interface BlockStreamManager extends BlockRecordInfo, BiConsumer, StateHashedListener { +public interface BlockStreamManager extends BlockRecordInfo, StateHashedListener { Bytes ZERO_BLOCK_HASH = Bytes.wrap(new byte[48]); /** 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 3b14c7befd54..92033bb322b0 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.node.app.blocks.impl; import static com.hedera.hapi.block.stream.BlockItem.ItemOneOfType.TRANSACTION_RESULT; @@ -31,6 +46,7 @@ import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.state.blockstream.BlockStreamInfo; import com.hedera.hapi.platform.state.PlatformState; +import com.hedera.node.app.blocks.BlockHashSigner; import com.hedera.node.app.blocks.BlockItemWriter; import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.BlockStreamService; @@ -40,7 +56,6 @@ import com.hedera.node.app.info.DiskStartupNetworks; import com.hedera.node.app.info.DiskStartupNetworks.InfoType; import com.hedera.node.app.records.impl.BlockRecordInfoUtils; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.BlockRecordStreamConfig; import com.hedera.node.config.data.BlockStreamConfig; @@ -90,7 +105,7 @@ public class BlockStreamManagerImpl implements BlockStreamManager { private final BlockStreamWriterMode streamWriterType; private final int hashCombineBatchSize; private final int serializationBatchSize; - private final TssBaseService tssBaseService; + private final BlockHashSigner blockHashSigner; private final SemanticVersion version; private final SemanticVersion hapiVersion; private final ExecutorService executor; @@ -154,17 +169,17 @@ private record PendingBlock( @Inject public BlockStreamManagerImpl( + @NonNull final BlockHashSigner blockHashSigner, @NonNull final Supplier writerSupplier, @NonNull final ExecutorService executor, @NonNull final ConfigProvider configProvider, - @NonNull final TssBaseService tssBaseService, @NonNull final BoundaryStateChangeListener boundaryStateChangeListener, @NonNull final InitialStateHash initialStateHash, @NonNull final SemanticVersion version) { + this.blockHashSigner = requireNonNull(blockHashSigner); this.version = requireNonNull(version); this.writerSupplier = requireNonNull(writerSupplier); this.executor = requireNonNull(executor); - this.tssBaseService = requireNonNull(tssBaseService); this.boundaryStateChangeListener = requireNonNull(boundaryStateChangeListener); requireNonNull(configProvider); final var config = configProvider.getConfiguration(); @@ -334,9 +349,9 @@ public void endRound(@NonNull final State state, final long roundNum) { // Update in-memory state to prepare for the next block lastBlockHash = blockHash; writer = null; - // Request the ledger signature for the block hash. - // The boundary timestamp plus nanos will be used for the TssShareSignature transaction's valid start - tssBaseService.requestLedgerSignature(blockHash.toByteArray(), asInstant(boundaryTimestamp)); + blockHashSigner + .signFuture(blockHash) + .thenAccept(signature -> finishProofWithSignature(blockHash, signature)); final var exportNetworkToDisk = switch (diskNetworkExport) { @@ -389,20 +404,21 @@ public long blockNo() { } /** + * If still pending, finishes the block proof for the block with the given hash using the given direct signature. + *

* Synchronized to ensure that block proofs are always written in order, even in edge cases where multiple * pending block proofs become available at the same time. * - * @param message the block hash to finish the block proof for - * @param signature the signature to use in the block proof + * @param blockHash the block hash to finish the block proof for + * @param blockSignature the signature to use in the block proof */ - @Override - public synchronized void accept(@NonNull final byte[] message, @NonNull final byte[] signature) { + private synchronized void finishProofWithSignature( + @NonNull final Bytes blockHash, @NonNull final Bytes blockSignature) { // Find the block whose hash is the signed message, tracking any sibling hashes // needed for indirect proofs of earlier blocks along the way long blockNumber = Long.MIN_VALUE; boolean impliesIndirectProof = false; final List> siblingHashes = new ArrayList<>(); - final var blockHash = Bytes.wrap(message); for (final var block : pendingBlocks) { if (impliesIndirectProof) { siblingHashes.add(List.of(block.siblingHashes())); @@ -418,7 +434,6 @@ public synchronized void accept(@NonNull final byte[] message, @NonNull final by return; } // Write proofs for all pending blocks up to and including the signed block number - final var blockSignature = Bytes.wrap(signature); while (!pendingBlocks.isEmpty() && pendingBlocks.peek().number() <= blockNumber) { final var block = pendingBlocks.poll(); final var proof = block.proofBuilder() 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 771959702546..c17fc021ad60 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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.info; import static com.hedera.hapi.util.HapiUtils.parseAccount; @@ -8,19 +23,10 @@ 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; import com.hedera.node.app.service.addressbook.impl.ReadableNodeStoreImpl; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.node.app.tss.handlers.TssUtils; -import com.hedera.node.app.tss.stores.ReadableTssStoreImpl; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.NetworkAdminConfig; -import com.hedera.node.config.data.TssConfig; import com.hedera.node.internal.network.Network; import com.hedera.node.internal.network.NodeMetadata; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -31,7 +37,6 @@ import com.swirlds.platform.config.legacy.LegacyConfigPropertiesLoader; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.roster.RosterRetriever; -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; @@ -62,7 +67,6 @@ public class DiskStartupNetworks implements StartupNetworks { public static final Pattern ROUND_DIR_PATTERN = Pattern.compile("\\d+"); private final ConfigProvider configProvider; - private final TssBaseService tssBaseService; private boolean isArchived = false; @@ -71,7 +75,6 @@ public class DiskStartupNetworks implements StartupNetworks { */ public enum InfoType { ROSTER, - TSS_KEYS, NODE_DETAILS, } @@ -84,10 +87,8 @@ private enum AssetUse { MIGRATION, } - public DiskStartupNetworks( - @NonNull final ConfigProvider configProvider, @NonNull final TssBaseService tssBaseService) { + public DiskStartupNetworks(@NonNull final ConfigProvider configProvider) { this.configProvider = requireNonNull(configProvider); - this.tssBaseService = tssBaseService; } @Override @@ -177,34 +178,18 @@ public Network migrationNetworkOrThrow() { public static void writeNetworkInfo( @NonNull final State state, @NonNull final Path path, @NonNull final Set infoTypes) { requireNonNull(state); - final var tssStore = new ReadableTssStoreImpl(state.getReadableStates(TssBaseService.NAME)); final var nodeStore = new ReadableNodeStoreImpl(state.getReadableStates(AddressBookService.NAME)); - final var rosterStore = new ReadableRosterStoreImpl(state.getReadableStates(RosterService.NAME)); Optional.ofNullable(RosterRetriever.retrieveActiveOrGenesisRoster(state)) .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( infoTypes.contains(InfoType.ROSTER) ? entry : null, - infoTypes.contains(InfoType.NODE_DETAILS) ? node : null, - infoTypes.contains(InfoType.TSS_KEYS) ? encryptionKey : Bytes.EMPTY)); + infoTypes.contains(InfoType.NODE_DETAILS) ? node : null)); }); network.nodeMetadata(nodeMetadata); - if (infoTypes.contains(InfoType.TSS_KEYS)) { - 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())); - } - } tryToExport(network.build(), path); }); } @@ -257,7 +242,6 @@ public static void tryToExport(@NonNull final Network network, @NonNull final Pa .deleted(false) .adminKey(Key.DEFAULT) .build()) - .tssEncryptionKey(Bytes.EMPTY) .build(); }) .toList()) @@ -279,14 +263,11 @@ private Optional loadNetwork( log.info("Checking for {} network info at {}", use, path.toAbsolutePath()); final var maybeNetwork = loadNetworkFrom(path); maybeNetwork.ifPresentOrElse( - network -> { - log.info( - " -> Parsed {} network info for N={} nodes from {}", - use, - network.nodeMetadata().size(), - path.toAbsolutePath()); - assertValidTssKeys(network); - }, + network -> log.info( + " -> Parsed {} network info for N={} nodes from {}", + use, + network.nodeMetadata().size(), + path.toAbsolutePath()), () -> log.info(" -> N/A")); return maybeNetwork; } @@ -332,43 +313,6 @@ private Optional genesisNetworkFromConfigTxt(@NonNull final Configurati } } - /** - * 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 - */ - private void assertValidTssKeys(@NonNull final Network network) { - final var expectedLedgerId = network.ledgerId(); - if (!Bytes.EMPTY.equals(expectedLedgerId)) { - final var roster = new Roster(network.nodeMetadata().stream() - .map(metadata -> new RosterEntry( - metadata.nodeOrThrow().nodeId(), - metadata.nodeOrThrow().weight(), - metadata.nodeOrThrow().gossipCaCertificate(), - metadata.nodeOrThrow().gossipEndpoint())) - .toList()); - final var maxSharesPerNode = configProvider - .getConfiguration() - .getConfigData(TssConfig.class) - .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) - .map(msg -> tssBaseService.getTssMessageFromBytes(Bytes.wrap(msg), directory)) - .toList(); - final var actualLedgerId = tssBaseService.ledgerIdFrom(directory, tssMessages); - if (!expectedLedgerId.equals(actualLedgerId)) { - throw new IllegalArgumentException("Ledger id '" + actualLedgerId.toHex() - + "' does not match expected '" + expectedLedgerId.toHex() + "'"); - } - } - } - /** * Attempts to archive the given segments in the given configuration. * diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java index f0e5f0a5857e..d49ccd596af5 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -26,7 +26,6 @@ import com.hedera.node.app.service.schedule.ScheduleService; import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.util.UtilService; -import com.hedera.node.app.tss.TssBaseService; import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Inject; import javax.inject.Singleton; @@ -121,7 +120,6 @@ public String getServiceName(@NonNull final TransactionBody txBody) { }; case NODE_CREATE, NODE_DELETE, NODE_UPDATE -> AddressBookService.NAME; - case TSS_MESSAGE, TSS_VOTE, TSS_SHARE_SIGNATURE -> TssBaseService.NAME; default -> NON_EXISTING_SERVICE; }; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java index a2083ad9920a..f88a005c753c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -58,9 +58,6 @@ import com.hedera.node.app.service.token.impl.ReadableStakingInfoStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenRelationStoreImpl; import com.hedera.node.app.service.token.impl.ReadableTokenStoreImpl; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.node.app.tss.stores.ReadableTssStore; -import com.hedera.node.app.tss.stores.ReadableTssStoreImpl; import com.swirlds.platform.state.PlatformMerkleStateRoot; import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.ReadablePlatformStateStore; @@ -122,7 +119,6 @@ private static Map, StoreEntry> createFactoryMap() { ReadablePlatformStateStore.class, new StoreEntry(PlatformStateService.NAME, ReadablePlatformStateStore::new)); newMap.put(ReadableRosterStore.class, new StoreEntry(RosterService.NAME, ReadableRosterStoreImpl::new)); - newMap.put(ReadableTssStore.class, new StoreEntry(TssBaseService.NAME, ReadableTssStoreImpl::new)); return Collections.unmodifiableMap(newMap); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java index 721b2de37caf..f2eb1efd5a02 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -44,8 +44,6 @@ import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.node.app.tss.stores.WritableTssStore; import com.swirlds.config.api.Configuration; import com.swirlds.platform.state.service.WritableRosterStore; import com.swirlds.state.State; @@ -111,10 +109,6 @@ private static Map, StoreEntry> createFactoryMap() { newMap.put( WritableRosterStore.class, new StoreEntry(RosterService.NAME, (states, config, metrics) -> new WritableRosterStore(states))); - // TSSBase Service - newMap.put( - WritableTssStore.class, - new StoreEntry(TssBaseService.NAME, (states, config, metrics) -> new WritableTssStore(states))); return Collections.unmodifiableMap(newMap); } 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 deleted file mode 100644 index 99c7555d4342..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/RosterToKey.java +++ /dev/null @@ -1,37 +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; -/** - * 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 b14cc9e58f84..eea7aa1e534f 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,189 +16,21 @@ package com.hedera.node.app.tss; -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.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; -import com.hedera.node.app.version.ServicesSoftwareVersion; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.config.api.Configuration; -import com.swirlds.platform.system.InitTrigger; -import com.swirlds.state.State; import com.swirlds.state.lifecycle.Service; import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.Consumer; /** - * Provides the network's threshold signature scheme (TSS) capability and, - * as a side effect, the ledger id; as this is exactly the same as the TSS - * public key. + * The service for the inexact weight TSS implementation to be used before completion of exact-weight TSS + * scheme. This service is now responsible only for registering schemas to deserialize, and then remove, + * the states added in {@code 0.56.0} and {@code 0.58.0}. */ +@Deprecated(forRemoval = true, since = "0.59.0") public interface TssBaseService extends Service { - /** - * Since the roster service has to decide to adopt the candidate roster - * based on the available key material, the TSS service must be migrated - * before the roster service. - */ - int MIGRATION_ORDER = RosterService.MIGRATION_ORDER - 1; - String NAME = "TssBaseService"; - /** - * The status of the TSS service relative to a given roster and ledger id. - */ - enum Status { - /** - * The service cannot yet recover the expected ledger id from its current key material for the roster. - */ - PENDING_LEDGER_ID, - /** - * The TSS service is ready to sign. - */ - READY, - } - @NonNull @Override 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. - * - * @param roster the candidate roster - * @param ledgerId the expected ledger id - * @param tssBaseStore the store to read the TSS base state from - * @return the status of the TSS service - */ - Status getStatus(@NonNull Roster roster, @NonNull Bytes ledgerId, @NonNull ReadableTssStoreImpl tssBaseStore); - - /** - * Bootstraps the TSS service for the given roster in the given context. - * - * @param roster the network genesis roster - * @param context the TSS context to use for bootstrapping - * @param ledgerIdConsumer the consumer of the ledger id, to receive the ledger id as soon as it is available - */ - void bootstrapLedgerId( - @NonNull Roster roster, @NonNull HandleContext context, @NonNull Consumer ledgerIdConsumer); - - /** - * Starts the process of keying a candidate roster with TSS key material. - * - * @param roster the candidate roster to key - * @param context the TSS context - */ - void setCandidateRoster(@NonNull Roster roster, @NonNull HandleContext context); - - /** - * Requests a ledger signature on a message hash. The ledger signature is computed asynchronously and returned - * to all consumers that have been registered through {@link #registerLedgerSignatureConsumer}. - * - * @param messageHash The hash of the message to be signed by the ledger. - * @param lastUsedConsensusTime The last used consensus time in the round. - */ - void requestLedgerSignature(@NonNull byte[] messageHash, @NonNull Instant lastUsedConsensusTime); - - /** - * Registers a consumer of the message hash and the ledger signature on the message hash. - * - * @param consumer the consumer of ledger signatures and message hashes. - */ - void registerLedgerSignatureConsumer(@NonNull BiConsumer consumer); - - /** - * Unregisters a consumer of the message hash and the ledger signature on the message hash. - * - * @param consumer the consumer of ledger signatures and message hashes to unregister. - */ - void unregisterLedgerSignatureConsumer(@NonNull BiConsumer consumer); - - /** - * Returns the {@link TssHandlers} for this service. - * - * @return the handlers - */ - TssHandlers tssHandlers(); - - /** - * Returns the active roster for the given network state. - * If the network is not at genesis and is an upgrade, then the active roster is determined based on the - * votes in the state. If the network is at genesis, then the active roster is the genesis roster. - * - * @param state the network state - * @param trigger the initialization trigger - * @param serviceMigrator the service migrator - * @param version the services software version - * @param configuration the configuration - * @return the active roster - */ - Roster chooseRosterForNetwork( - @NonNull State state, - @NonNull InitTrigger trigger, - @NonNull ServiceMigrator serviceMigrator, - @NonNull ServicesSoftwareVersion version, - @NonNull Configuration configuration, - @NonNull Roster overrideRoster); - - /** - * Regenerates the key material for the active roster. This happens each time the active roster is updated. - * This is only called when the tss key candidate roster feature flag is enabled. - * - * @param state the network state - */ - void regenerateKeyMaterial(@NonNull State state); - - /** - * Generates the participant directory for the active roster. - * @param state the network state - */ - void ensureParticipantDirectoryKnown(@NonNull State state); - - /** - * Returns the ledger id from the given TSS participant directory and TSS messages. - * @param directory the participant directory - * @param tssMessages the TSS messages - * @return the ledger id - */ - Bytes ledgerIdFrom(@NonNull TssParticipantDirectory directory, @NonNull List tssMessages); - - /** - * Returns the TSS message from the given bytes and participant directory. - * @param wrap the bytes - * @param directory the participant directory - * @return the TSS message - */ - TssMessage getTssMessageFromBytes(Bytes wrap, TssParticipantDirectory directory); - - /** - * Manages and does work based on the TSS status. - * 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( - 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 deleted file mode 100644 index f31d293f32c8..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceComponent.java +++ /dev/null @@ -1,62 +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; - -import com.hedera.node.app.spi.AppContext; -import com.hedera.node.app.tss.api.TssLibrary; -import com.hedera.node.app.tss.handlers.TssMessageHandler; -import com.hedera.node.app.tss.handlers.TssShareSignatureHandler; -import com.hedera.node.app.tss.handlers.TssSubmissions; -import com.hedera.node.app.tss.handlers.TssVoteHandler; -import com.swirlds.metrics.api.Metrics; -import dagger.BindsInstance; -import dagger.Component; -import java.time.InstantSource; -import java.util.concurrent.Executor; -import javax.inject.Singleton; - -@Singleton -@Component -public interface TssBaseServiceComponent { - @Component.Factory - interface Factory { - TssBaseServiceComponent create( - @BindsInstance TssLibrary tssLibrary, - @BindsInstance InstantSource instantSource, - @BindsInstance AppContext appContext, - @BindsInstance Executor submissionExecutor, - @BindsInstance @TssLibraryExecutor Executor libraryExecutor, - @BindsInstance Metrics metrics, - @BindsInstance TssBaseService tssBaseService); - } - - TssMetrics tssMetrics(); - - TssMessageHandler tssMessageHandler(); - - TssVoteHandler tssVoteHandler(); - - TssShareSignatureHandler tssShareSignatureHandler(); - - TssSubmissions tssSubmissions(); - - 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 aa0d2292f699..b0414982b49e 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,764 +16,24 @@ 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; 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; -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; -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.V0580TssBaseSchema; -import com.hedera.node.app.tss.stores.ReadableTssStore; -import com.hedera.node.app.tss.stores.ReadableTssStoreImpl; -import com.hedera.node.app.version.ServicesSoftwareVersion; -import com.hedera.node.config.data.TssConfig; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.common.utility.CommonUtils; -import com.swirlds.config.api.Configuration; -import com.swirlds.metrics.api.Metrics; -import com.swirlds.platform.roster.RosterUtils; -import com.swirlds.platform.state.service.ReadableRosterStore; -import com.swirlds.platform.system.InitTrigger; -import com.swirlds.state.State; +import com.hedera.node.app.tss.schemas.V059TssBaseSchema; 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; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.LongFunction; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Default implementation of the {@link TssBaseService}. */ +@Deprecated(forRemoval = true, since = "0.59.0") public class TssBaseServiceImpl implements TssBaseService { - private static final Logger log = LogManager.getLogger(TssBaseServiceImpl.class); - - /** - * Copy-on-write list to avoid concurrent modification exceptions if a consumer unregisters - * itself in its callback. - */ - private final List> consumers = new CopyOnWriteArrayList<>(); - - private final TssMetrics tssMetrics; - private final TssLibrary tssLibrary; - private final TssHandlers tssHandlers; - private final TssSubmissions tssSubmissions; - private final Executor tssLibraryExecutor; - private final Executor signingExecutor; - private final TssKeysAccessor tssKeysAccessor; - private final TssDirectoryAccessor tssDirectoryAccessor; - 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, - @NonNull final Executor signingExecutor, - @NonNull final Executor submissionExecutor, - @NonNull final TssLibrary tssLibrary, - @NonNull final Executor tssLibraryExecutor, - @NonNull final Metrics metrics) { - requireNonNull(appContext); - this.tssLibrary = requireNonNull(tssLibrary); - this.signingExecutor = requireNonNull(signingExecutor); - this.tssLibraryExecutor = requireNonNull(tssLibraryExecutor); - this.appContext = requireNonNull(appContext); - final var component = DaggerTssBaseServiceComponent.factory() - .create( - tssLibrary, - appContext.instantSource(), - appContext, - submissionExecutor, - tssLibraryExecutor, - metrics, - this); - this.tssKeysAccessor = component.tssKeysAccessor(); - this.tssDirectoryAccessor = component.tssDirectoryAccessor(); - this.tssMetrics = component.tssMetrics(); - this.tssHandlers = new TssHandlers( - component.tssMessageHandler(), component.tssVoteHandler(), component.tssShareSignatureHandler()); - this.tssSubmissions = component.tssSubmissions(); - this.tssCryptographyManager = component.tssCryptographyManager(); - } - @Override public void registerSchemas(@NonNull final SchemaRegistry registry) { requireNonNull(registry); registry.register(new V0560TssBaseSchema()); registry.register(new V0580TssBaseSchema()); - } - - @Override - public Status getStatus( - @NonNull final Roster roster, - @NonNull final Bytes ledgerId, - @NonNull final ReadableTssStoreImpl tssBaseStore) { - requireNonNull(roster); - requireNonNull(ledgerId); - requireNonNull(tssBaseStore); - // (TSS-FUTURE) Determine if the given ledger id can be recovered from the key material for the given roster - return PENDING_LEDGER_ID; - } - - @Override - public void bootstrapLedgerId( - @NonNull final Roster roster, - @NonNull final HandleContext context, - @NonNull final Consumer ledgerIdConsumer) { - requireNonNull(roster); - requireNonNull(context); - requireNonNull(ledgerIdConsumer); - // (TSS-FUTURE) Create a real ledger id - ledgerIdConsumer.accept(Bytes.EMPTY); - } - - @Override - public void setCandidateRoster(@NonNull final Roster candidateRoster, @NonNull final HandleContext context) { - requireNonNull(candidateRoster); - - // we keep track of the starting point of the candidate roster's lifecycle - final Instant candidateRosterLifecycleStart = InstantSource.system().instant(); - tssMetrics.trackCandidateRosterLifecycleStart(candidateRosterLifecycleStart); - // (TSS-FUTURE) Implement `keyActiveRoster` - // https://github.com/hashgraph/hedera-services/issues/16166 - - final var maxSharesPerNode = - context.configuration().getConfigData(TssConfig.class).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 sourceRosterHash = RosterUtils.hash(activeRoster).getBytes(); - - final var tssPrivateShares = tssKeysAccessor.accessTssKeys().activeRosterShares(); - - final var candidateRosterHash = RosterUtils.hash(candidateRoster).getBytes(); - // FUTURE - instead of an arbitrary counter here, use the share index from the private share - final var shareIndex = new AtomicInteger(0); - for (final var tssPrivateShare : tssPrivateShares) { - CompletableFuture.runAsync( - () -> { - final var msg = tssLibrary.generateTssMessage(candidateDirectory, tssPrivateShare); - final var tssMessage = TssMessageTransactionBody.newBuilder() - .sourceRosterHash(sourceRosterHash) - .targetRosterHash(candidateRosterHash) - .shareIndex(shareIndex.getAndAdd(1)) - .tssMessage(Bytes.wrap(msg.toBytes())) - .build(); - tssSubmissions.submitTssMessage(tssMessage, context); - }, - tssLibraryExecutor) - .exceptionally(e -> { - log.error("Error generating tssMessage", e); - return null; - }); - } - } - - @Override - public void requestLedgerSignature( - @NonNull final byte[] messageHash, @NonNull final Instant lastUsedConsensusTime) { - requireNonNull(messageHash); - requireNonNull(lastUsedConsensusTime); - final var mockSignature = noThrowSha384HashOf(messageHash); - CompletableFuture.runAsync( - () -> { - if (appContext - .configSupplier() - .get() - .getConfigData(TssConfig.class) - .signWithLedgerId()) { - submitShareSignatures(messageHash, lastUsedConsensusTime); - } else { - // This is only for testing purposes when the candidate roster is - // not enabled - consumers.forEach(consumer -> { - try { - consumer.accept(messageHash, mockSignature); - } catch (Exception e) { - log.error( - "Failed to provide signature {} on message {} to consumer {}", - CommonUtils.hex(mockSignature), - CommonUtils.hex(messageHash), - consumer, - e); - } - }); - } - }, - signingExecutor); - } - - private void submitShareSignatures(final byte[] messageHash, final Instant lastUsedConsensusTime) { - final var tssPrivateShares = tssKeysAccessor.accessTssKeys().activeRosterShares(); - final var activeRoster = tssKeysAccessor.accessTssKeys().activeRosterHash(); - long nanosOffset = 1; - for (final var privateShare : tssPrivateShares) { - final var signature = tssLibrary.sign(privateShare, messageHash); - final var tssShareSignatureBody = TssShareSignatureTransactionBody.newBuilder() - .messageHash(Bytes.wrap(messageHash)) - .shareSignature(Bytes.wrap(signature.signature().toBytes())) - .shareIndex(privateShare.shareId()) - .rosterHash(activeRoster) - .build(); - tssSubmissions.submitTssShareSignature( - tssShareSignatureBody, lastUsedConsensusTime.plusNanos(nanosOffset++)); - } - } - - @Override - public void registerLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { - requireNonNull(consumer); - consumers.add(consumer); - } - - @Override - public void unregisterLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { - requireNonNull(consumer); - consumers.remove(consumer); - } - - @Override - public TssHandlers tssHandlers() { - return tssHandlers; - } - - @Override - @NonNull - public Roster chooseRosterForNetwork( - @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()) { - return overrideRoster; - } - final var activeRoster = retrieveActiveOrGenesisRoster(state); - if (trigger != GENESIS) { - final var creatorVersion = requireNonNull(serviceMigrator.creationVersionOf(state)); - final var isUpgrade = version.compareTo(new ServicesSoftwareVersion(creatorVersion)) > 0; - // If we are not at genesis and the software version is newer than the state version, then we need to - // pick the active roster or candidate roster based on votes in the state - if (isUpgrade) { - final var candidateRosterHash = getCandidateRosterHash(state); - final var tssStore = new ReadableStoreFactory(state).getStore(ReadableTssStore.class); - if (hasEnoughWeight(activeRoster, candidateRosterHash, tssStore)) { - final ReadableKVState rosters = requireNonNull( - state.getReadableStates(RosterService.NAME).get(V0540RosterSchema.ROSTER_KEY)); - // It should be impossible to set a candidate roster hash that doesn't exist - return requireNonNull(rosters.get(new ProtoBytes(candidateRosterHash))); - } - } - } - return activeRoster; - } - - @Override - public void regenerateKeyMaterial(@NonNull final State state) { - tssKeysAccessor.generateKeyMaterialForActiveRoster(state); - } - - @Override - public void ensureParticipantDirectoryKnown(@NonNull final State state) { - tssDirectoryAccessor.generateTssParticipantDirectory(state); - } - - @Override - public Bytes ledgerIdFrom( - @NonNull final TssParticipantDirectory directory, @NonNull final List tssMessages) { - requireNonNull(directory); - requireNonNull(tssMessages); - final var publicShares = tssLibrary.computePublicShares(directory, tssMessages); - final var publicKey = tssLibrary.aggregatePublicShares(publicShares); - return Bytes.wrap(publicKey.toBytes()); - } - - /** - * Notifies the consumers that a signature has been received for the message hash. - * - * @param messageHash the message hash - * @param signature the signature - */ - public void notifySignature(@NonNull final byte[] messageHash, @NonNull final byte[] signature) { - requireNonNull(messageHash); - requireNonNull(signature); - consumers.forEach(consumer -> { - try { - consumer.accept(messageHash, signature); - } catch (Exception e) { - log.error( - "Failed to provide signature {} on message {} to consumer {}", - CommonUtils.hex(signature), - CommonUtils.hex(messageHash), - consumer, - e); - } - }); - } - - /** - * Returns true if there exists a vote bitset for the given candidate roster hash whose received weight - * is at least 1/3 of the total weight of the active roster. - * - * @param activeRoster the active roster - * @param rosterHash the candidate roster hash - * @param tssBaseStore the TSS store - * @return true if the threshold has been reached, false otherwise - */ - private static boolean hasEnoughWeight( - @NonNull final Roster activeRoster, - @NonNull final Bytes rosterHash, - @NonNull final ReadableTssStore tssBaseStore) { - // Also get the total active roster weight - long activeRosterTotalWeight = 0; - final var voteWeightMap = new LinkedHashMap(); - for (final var rosterEntry : activeRoster.rosterEntries()) { - activeRosterTotalWeight += rosterEntry.weight(); - final var tssVoteMapKey = new TssVoteMapKey(rosterHash, rosterEntry.nodeId()); - if (tssBaseStore.exists(tssVoteMapKey)) { - final var voteBody = tssBaseStore.getVote(tssVoteMapKey); - voteWeightMap.merge(voteBody.tssVote(), rosterEntry.weight(), Long::sum); - } - } - // Use hasMetThreshold to check if any of the votes have met the threshold - for (final var voteWeight : voteWeightMap.values()) { - if (hasMetThreshold(voteWeight, activeRosterTotalWeight)) { - return true; - } - } - return false; - } - - @Override - public TssMessage getTssMessageFromBytes(Bytes wrap, TssParticipantDirectory directory) { - return tssLibrary.getTssMessageFromBytes(wrap, directory); - } - - @Override - 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; + registry.register(new V059TssBaseSchema()); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBlockHashSigner.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBlockHashSigner.java new file mode 100644 index 000000000000..7de486798567 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBlockHashSigner.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024-2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.tss; + +import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.blocks.BlockHashSigner; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * FUTURE + * ------ + * A {@link BlockHashSigner} that uses whatever parts of the TSS protocol are enabled to sign blocks. + * That is, + *

    + *
  1. If neither hinTS nor history proofs are enabled: + *
      + *
    • Is always ready to sign.
    • + *
    • To sign, schedules async delivery of the SHA-384 hash of the block hash as its "signature".
    • + *
    + *
  2. If only hinTS is enabled: + *
      + *
    • Is not ready to sign during bootstrap phase until the genesis hinTS construction has completed + * preprocessing and reached a consensus verification key.
    • + *
    • To sign, initiates async aggregation of partial hinTS signatures from the active construction.
    • + *
    + *
  3. + *
  4. If only history proofs are enabled: + *
      + *
    • Is not ready to sign during bootstrap phase until the history service has collated as many + * Schnorr keys as it reasonably can for the genesis TSS address book; and accumulated signatures + * from a strong minority of those keys on the genesis TSS address book hash with empty metadata to + * derive a consensus genesis proof.
    • + *
    • To sign, schedules async delivery of the SHA-384 hash of the block hash as its "signature" + * but assembles a full TSS signature with proof of empty metadata in the TSS address book whose + * roster would have performed the hinTS signing.
    • + *
    + *
  5. + *
  6. If both hinTS and history proofs are enabled: + *
      + *
    • Is not ready to sign during bootstrap phase until the genesis hinTS construction has completed + * preprocessing and reached a consensus verification key; and until the history service has collated + * as many Schnorr keys as it reasonably can for the genesis TSS address book; and accumulated signatures + * from a strong minority of those keys on the genesis TSS address book hash with the hinTS verification + * key as its metadata to derive a consensus genesis proof.
    • + *
    • To sign, initiates async aggregation of partial hinTS signatures from the active construction, + * packaging this async delivery into a full TSS signature with proof of the hinTS verification key as + * the metadata of the active TSS address book.
    • + *
    + *
  7. + *
+ */ +@Singleton +public class TssBlockHashSigner implements BlockHashSigner { + @Inject + public TssBlockHashSigner() { + // Dagger2 + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public CompletableFuture signFuture(@NonNull final Bytes blockHash) { + requireNonNull(blockHash); + return CompletableFuture.supplyAsync(() -> noThrowSha384HashOf(blockHash)); + } +} 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 deleted file mode 100644 index 122d0c89c096..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java +++ /dev/null @@ -1,143 +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; - -import static com.hedera.node.app.tss.handlers.TssUtils.getTssMessages; -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.services.auxiliary.tss.TssMessageTransactionBody; -import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; -import com.hedera.node.app.spi.AppContext; -import com.hedera.node.app.tss.api.TssLibrary; -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; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * This is yet to be implemented - */ -@Singleton -public class TssCryptographyManager { - private static final Logger log = LogManager.getLogger(TssCryptographyManager.class); - - private final Executor libraryExecutor; - private final TssMetrics tssMetrics; - private final TssLibrary tssLibrary; - private final InstantSource instantSource; - private final AppContext.Gossip gossip; - - @Inject - public TssCryptographyManager( - @NonNull final TssLibrary tssLibrary, - @NonNull final AppContext appContext, - @NonNull @TssLibraryExecutor final Executor libraryExecutor, - @NonNull final TssMetrics tssMetrics, - @NonNull final InstantSource instantSource) { - this.tssLibrary = requireNonNull(tssLibrary); - this.gossip = requireNonNull(appContext.gossip()); - this.libraryExecutor = requireNonNull(libraryExecutor); - this.tssMetrics = requireNonNull(tssMetrics); - this.instantSource = requireNonNull(instantSource); - } - - /** - * A signed vote containing the ledger id with a bit set denoting the threshold TSS messages used to compute it. - */ - public record Vote( - @NonNull BlsPublicKey ledgerPublicKey, @NonNull Signature signature, @NonNull BitSet thresholdMessages) { - public @NonNull Bytes ledgerId() { - return Bytes.wrap(ledgerPublicKey.toBytes()); - } - - public @NonNull Bytes bitSet() { - return Bytes.wrap(thresholdMessages.toByteArray()); - } - } - - /** - * Schedules work to try to compute a signed vote for the new key material of a roster referenced by the - * 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 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 TssParticipantDirectory directory, - @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; - }); - } - return CompletableFuture.completedFuture(null); - } - - /** - * Schedules work to compute and sign the ledger id if given {@link TssMessageTransactionBody} messages contain - * a threshold number of valid {@link TssMessage}s. - * - * @param tssMessageBodies the list of TSS message bodies - * @param tssParticipantDirectory the TSS participant directory - * @return a future that resolves to the ledger id and signature if the threshold is met - */ - private CompletableFuture computeVote( - @NonNull final List tssMessageBodies, - @NonNull final TssParticipantDirectory tssParticipantDirectory) { - return CompletableFuture.supplyAsync(() -> getVote(tssMessageBodies, tssParticipantDirectory), libraryExecutor); - } - - @Nullable - public Vote getVote( - final @NonNull List tssMessageBodies, - final @NonNull TssParticipantDirectory tssParticipantDirectory) { - final var result = voteForValidMessages(tssMessageBodies, tssParticipantDirectory, tssLibrary); - if (result.isEmpty()) { - return null; - } - 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 deleted file mode 100644 index c4fca0e45523..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java +++ /dev/null @@ -1,116 +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; - -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.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; -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 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; - -/** - * Provides access to the {@link TssParticipantDirectory} for the active roster. - */ -@Singleton -public class TssDirectoryAccessor { - private final Supplier configurationSupplier; - - /** - * Non-final because it is lazy-initialized once state is available. - */ - private TssParticipantDirectory tssParticipantDirectory; - - @Inject - public TssDirectoryAccessor(@NonNull final AppContext appContext) { - this.configurationSupplier = requireNonNull(appContext).configSupplier(); - } - - /** - * Generates the participant directory for the active roster. - * @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 tssParticipantDirectory; - } - final var activeRoster = requireNonNull(rosterStore.getActiveRoster()); - final var maxSharesPerNode = - configurationSupplier.get().getConfigData(TssConfig.class).maxSharesPerNode(); - tssParticipantDirectory = computeParticipantDirectory(activeRoster, maxSharesPerNode, encryptionKeyFn); - 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 - * @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/TssKeyingStatus.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeyingStatus.java deleted file mode 100644 index 6379ab07537b..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeyingStatus.java +++ /dev/null @@ -1,44 +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; -/** - * 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 deleted file mode 100644 index 1e6fb02d2d44..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java +++ /dev/null @@ -1,122 +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; - -import static com.hedera.node.app.tss.handlers.TssUtils.getTssMessages; -import static com.hedera.node.app.tss.handlers.TssUtils.getValidMessages; -import static java.util.Objects.requireNonNull; - -import com.google.common.annotations.VisibleForTesting; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -import com.hedera.cryptography.tss.api.TssPrivateShare; -import com.hedera.cryptography.tss.api.TssPublicShare; -import com.hedera.node.app.store.ReadableStoreFactory; -import com.hedera.node.app.tss.api.TssLibrary; -import com.hedera.node.app.tss.stores.ReadableTssStore; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.platform.state.service.ReadableRosterStore; -import com.swirlds.state.State; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Provides access to the key material for the threshold signature scheme (TSS) for the active roster. - */ -@Singleton -public class TssKeysAccessor { - private final TssLibrary tssLibrary; - private TssKeysAccessor.TssKeys tssKeys; - private final TssDirectoryAccessor tssDirectoryAccessor; - - @Inject - public TssKeysAccessor( - @NonNull final TssLibrary tssLibrary, @NonNull final TssDirectoryAccessor tssDirectoryAccessor) { - this.tssLibrary = requireNonNull(tssLibrary); - this.tssDirectoryAccessor = requireNonNull(tssDirectoryAccessor); - } - - /** - * Generates the key material for the active roster. - * - * @param state the state - */ - public void generateKeyMaterialForActiveRoster(@NonNull final State state) { - if (tssKeys != null) { - return; - } - final var storeFactory = new ReadableStoreFactory(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.activeParticipantDirectoryOrThrow(); - final var tssMessageBodies = tssStore.getMessagesForTarget(activeRosterHash); - final var validTssMessages = getTssMessages(tssMessageBodies, activeParticipantDirectory, tssLibrary); - final var activeRosterShares = getTssPrivateShares(activeParticipantDirectory, tssStore, activeRosterHash); - final var activeRosterPublicShares = - tssLibrary.computePublicShares(activeParticipantDirectory, validTssMessages); - final var totalShares = activeParticipantDirectory.getTotalShares(); - this.tssKeys = new TssKeysAccessor.TssKeys( - activeRosterShares, - activeRosterPublicShares, - activeRosterHash, - activeParticipantDirectory, - totalShares); - } - - @NonNull - private List getTssPrivateShares( - @NonNull final TssParticipantDirectory activeRosterParticipantDirectory, - @NonNull final ReadableTssStore tssStore, - @NonNull final Bytes activeRosterHash) { - final var tssMessages = getValidMessages( - tssStore.getMessagesForTarget(activeRosterHash), activeRosterParticipantDirectory, tssLibrary); - final var validTssMessages = getTssMessages(tssMessages, activeRosterParticipantDirectory, tssLibrary); - return tssLibrary.decryptPrivateShares(activeRosterParticipantDirectory, validTssMessages); - } - - /** - * Returns the TSS key material for the active roster. - * - * @return the TSS key material for the active roster - */ - public TssKeysAccessor.TssKeys accessTssKeys() { - return tssKeys; - } - - /** - * Represents the TSS key material for the active roster. - * - * @param activeRosterShares the active roster private shares - * @param activeRosterPublicShares the active roster public shares - * @param activeRosterHash the active roster hash - * @param activeParticipantDirectory the active participant directory - * @param totalShares the total number of shares - */ - public record TssKeys( - @NonNull List activeRosterShares, - @NonNull List activeRosterPublicShares, - @NonNull Bytes activeRosterHash, - @NonNull TssParticipantDirectory activeParticipantDirectory, - long totalShares) {} - - @VisibleForTesting - void setTssKeys(@NonNull final TssKeys tssKeys) { - this.tssKeys = tssKeys; - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssLibraryExecutor.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssLibraryExecutor.java deleted file mode 100644 index 8c443b33a751..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssLibraryExecutor.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.app.tss; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import javax.inject.Qualifier; - -/** - * Identifies the executor for TSS library operations. - */ -@Target({METHOD, PARAMETER, TYPE}) -@Retention(RUNTIME) -@Documented -@Qualifier -public @interface TssLibraryExecutor {} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssLibraryImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssLibraryImpl.java deleted file mode 100644 index 2c176ef0b8ea..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssLibraryImpl.java +++ /dev/null @@ -1,140 +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; - -import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; - -import com.hedera.cryptography.bls.BlsPrivateKey; -import com.hedera.cryptography.bls.BlsPublicKey; -import com.hedera.cryptography.bls.BlsSignature; -import com.hedera.cryptography.tss.api.TssMessage; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -import com.hedera.cryptography.tss.api.TssParticipantPrivateInfo; -import com.hedera.cryptography.tss.api.TssPrivateShare; -import com.hedera.cryptography.tss.api.TssPublicShare; -import com.hedera.cryptography.tss.api.TssService; -import com.hedera.cryptography.tss.api.TssShareSignature; -import com.hedera.cryptography.tss.impl.Groth21Service; -import com.hedera.node.app.spi.AppContext; -import com.hedera.node.app.tss.api.TssLibrary; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.state.lifecycle.info.NodeInfo; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.security.SecureRandom; -import java.util.List; -import java.util.function.Supplier; -import javax.inject.Inject; -// Future: This intermediate class can be deleted and use `TssService` directly in all the places needed. - -public class TssLibraryImpl implements TssLibrary { - private static final BlsPrivateKey AGGREGATED_PRIVATE_KEY = - BlsPrivateKey.create(SIGNATURE_SCHEMA, new SecureRandom()); - private byte[] message = new byte[0]; - - private TssService tssService; - private SecureRandom random = new SecureRandom(); - private Supplier selfNodeInfo; - - @Inject - public TssLibraryImpl(AppContext appContext) { - this.tssService = new Groth21Service(SIGNATURE_SCHEMA, random); - this.selfNodeInfo = appContext.selfNodeInfoSupplier(); - } - - @NonNull - @Override - public TssMessage generateTssMessage(@NonNull final TssParticipantDirectory tssParticipantDirectory) { - return tssService.genesisStage().generateTssMessage(tssParticipantDirectory); - } - - @NonNull - @Override - public TssMessage generateTssMessage( - @NonNull TssParticipantDirectory tssParticipantDirectory, @NonNull TssPrivateShare privateShare) { - return tssService.rekeyStage().generateTssMessage(tssParticipantDirectory, privateShare); - } - - @Override - public boolean verifyTssMessage( - @NonNull TssParticipantDirectory participantDirectory, @NonNull Bytes tssMessageBytes) { - try { - final var tssMessage = tssService.messageFromBytes(participantDirectory, tssMessageBytes.toByteArray()); - return tssService.rekeyStage().verifyTssMessage(participantDirectory, null, tssMessage); - } catch (Exception e) { - return false; - } - } - - @NonNull - @Override - public List decryptPrivateShares( - @NonNull TssParticipantDirectory participantDirectory, @NonNull List validTssMessages) { - final var tssPrivateInfo = - new TssParticipantPrivateInfo(selfNodeInfo.get().nodeId(), AGGREGATED_PRIVATE_KEY); - return tssService - .rekeyStage() - .shareExtractor(participantDirectory, validTssMessages) - .ownedPrivateShares(tssPrivateInfo); - } - - @NonNull - @Override - public List computePublicShares( - @NonNull TssParticipantDirectory participantDirectory, @NonNull List validTssMessages) { - return tssService - .rekeyStage() - .shareExtractor(participantDirectory, validTssMessages) - .allPublicShares(); - } - - @NonNull - @Override - public BlsPublicKey aggregatePublicShares(@NonNull List publicShares) { - return TssPublicShare.aggregate(publicShares); - } - - @NonNull - @Override - public BlsSignature aggregateSignatures(@NonNull List partialSignatures) { - return TssShareSignature.aggregate(partialSignatures); - } - - @NonNull - @Override - public TssShareSignature sign(@NonNull TssPrivateShare privateShare, @NonNull byte[] message) { - return privateShare.sign(message); - } - - @Override - public boolean verifySignature( - @NonNull final TssParticipantDirectory participantDirectory, - @NonNull final List publicShares, - @NonNull final TssShareSignature signature) { - // shareIds are starting from 1. So, when looking up in publicShares list, we - // need to subtract 1 - return signature.verify(publicShares.get(signature.shareId() - 1), message); - } - - @Override - public TssMessage getTssMessageFromBytes(Bytes tssMessage, TssParticipantDirectory participantDirectory) { - try { - return tssService.messageFromBytes(participantDirectory, tssMessage.toByteArray()); - } catch (Exception e) { - return null; - } - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssMetrics.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssMetrics.java deleted file mode 100644 index 58c8413e3a35..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssMetrics.java +++ /dev/null @@ -1,244 +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; - -import static java.util.Objects.requireNonNull; - -import com.google.common.annotations.VisibleForTesting; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.metrics.api.Counter; -import com.swirlds.metrics.api.LongGauge; -import com.swirlds.metrics.api.Metrics; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Duration; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A class to track all the metrics related to TSS functionalities. - */ -@Singleton -public class TssMetrics { - private static final Logger log = LogManager.getLogger(TssMetrics.class); - - private final Metrics metrics; - - private static final String TSS_MESSAGE_COUNTER_METRIC = "tss_message_total"; - private static final String TSS_MESSAGE_COUNTER_METRIC_DESC = - "total numbers of tss message transactions for roster "; - private final Map messagesPerCandidateRoster = new HashMap<>(); - - private static final String TSS_VOTE_COUNTER_METRIC = "tss_vote_total"; - private static final String TSS_VOTE_COUNTER_METRIC_DESC = "total numbers of tss vote transactions for roster "; - private final Map votesPerCandidateRoster = new HashMap<>(); - - private static final String TSS_SHARES_AGGREGATION_TIME = "tss_shares_aggregation_time"; - private static final String TSS_SHARES_AGGREGATION_TIME_DESC = - "the time it takes to compute shares from the key material"; - private static final LongGauge.Config TSS_SHARES_AGGREGATION_CONFIG = - new LongGauge.Config("app", TSS_SHARES_AGGREGATION_TIME).withDescription(TSS_SHARES_AGGREGATION_TIME_DESC); - private final LongGauge tssSharesAggregationTime; - - private static final String TSS_CANDIDATE_ROSTER_LIFECYCLE = "tss_candidate_roster_lifecycle"; - private static final String TSS_CANDIDATE_ROSTER_LIFECYCLE_DESC = "the lifecycle of the current candidate roster"; - private static final LongGauge.Config TSS_ROSTER_LIFECYCLE_CONFIG = new LongGauge.Config( - "app", TSS_CANDIDATE_ROSTER_LIFECYCLE) - .withDescription(TSS_CANDIDATE_ROSTER_LIFECYCLE_DESC); - - private static final String TSS_LEDGER_SIGNATURE_TIME = "tss_ledger_signature_time"; - private static final String TSS_LEDGER_SIGNATURE_TIME_DESC = - "the time it takes to to get ledger signature from the time it is requested"; - private static final LongGauge.Config TSS_LEDGER_SIGNATURE_TIME_CONFIG = - new LongGauge.Config("app", TSS_LEDGER_SIGNATURE_TIME).withDescription(TSS_LEDGER_SIGNATURE_TIME_DESC); - private final LongGauge tssLedgerSignatureTime; - - private static final String TSS_LEDGER_SIGNATURE_FAILURES_COUNTER = "tss_ledger_signature_failures_counter"; - private static final String TSS_LEDGER_SIGNATURE_FAILURES_COUNTER_DESC = - "The number of failures to generate a ledger signature"; - final Counter.Config TSS_LEDGER_SIGN_FAILURE_COUNTER = new Counter.Config( - "app", TSS_LEDGER_SIGNATURE_FAILURES_COUNTER) - .withDescription(TSS_LEDGER_SIGNATURE_FAILURES_COUNTER_DESC); - final Counter ledgerSignatureFailuresCounter; - - private final LongGauge tssCandidateRosterLifecycle; - - // local variable to track the start of candidate roster's lifecycle - private Instant candidateRosterLifecycleStart = null; - - /** - * Constructor for the TssMetrics. - * - * @param metrics the {@link Metrics} object where all metrics will be registered - */ - @Inject - public TssMetrics(@NonNull final Metrics metrics) { - this.metrics = requireNonNull(metrics, "metrics must not be null"); - tssCandidateRosterLifecycle = metrics.getOrCreate(TSS_ROSTER_LIFECYCLE_CONFIG); - tssSharesAggregationTime = metrics.getOrCreate(TSS_SHARES_AGGREGATION_CONFIG); - tssLedgerSignatureTime = metrics.getOrCreate(TSS_LEDGER_SIGNATURE_TIME_CONFIG); - ledgerSignatureFailuresCounter = metrics.getOrCreate(TSS_LEDGER_SIGN_FAILURE_COUNTER); - } - - /** - * Track the count of messages per candidate roster. - * - * @param targetRosterHash the {@link Bytes} of the candidate roster - */ - public void updateMessagesPerCandidateRoster(@NonNull final Bytes targetRosterHash) { - requireNonNull(targetRosterHash, "targetRosterHash must not be null"); - - // if this is the first message for this candidate roster, initialize new metric to track occurrences - if (!messagesPerCandidateRoster.containsKey(targetRosterHash)) { - final Counter.Config TSS_MESSAGE_TX_COUNTER = new Counter.Config("app", TSS_MESSAGE_COUNTER_METRIC) - .withDescription(TSS_MESSAGE_COUNTER_METRIC_DESC + targetRosterHash); - final Counter tssMessageTxCounter = metrics.getOrCreate(TSS_MESSAGE_TX_COUNTER); - tssMessageTxCounter.increment(); - messagesPerCandidateRoster.put(targetRosterHash, tssMessageTxCounter); - } else { - // if the metric is already present, just increment - getMessagesPerCandidateRoster(targetRosterHash).increment(); - } - } - - /** - * Track the count of votes per candidate roster. - * - * @param targetRosterHash the {@link Bytes} of the candidate roster - */ - public void updateVotesPerCandidateRoster(@NonNull final Bytes targetRosterHash) { - requireNonNull(targetRosterHash, "targetRosterHash must not be null"); - - // if this is the first vote for this candidate roster, initialize new metric to track occurrences - if (!votesPerCandidateRoster.containsKey(targetRosterHash)) { - final Counter.Config TSS_VOTE_TX_COUNTER = new Counter.Config("app", TSS_VOTE_COUNTER_METRIC) - .withDescription(TSS_VOTE_COUNTER_METRIC_DESC + targetRosterHash); - final Counter tssVoteTxCounter = metrics.getOrCreate(TSS_VOTE_TX_COUNTER); - tssVoteTxCounter.increment(); - votesPerCandidateRoster.put(targetRosterHash, tssVoteTxCounter); - } else { - // if the metric is already present, just increment - getVotesPerCandidateRoster(targetRosterHash).increment(); - } - } - - /** - * Track when the vote for candidate roster is closed. - * - * @param rosterLifecycleEndTime the time at which the candidate roster is set - */ - public void updateCandidateRosterLifecycle(@NonNull final Instant rosterLifecycleEndTime) { - requireNonNull(rosterLifecycleEndTime, "rosterLifecycleEndTime must not be null"); - if (candidateRosterLifecycleStart != null) { - tssCandidateRosterLifecycle.set(Duration.between(candidateRosterLifecycleStart, rosterLifecycleEndTime) - .toMillis()); - } - } - - /** - * Track when the candidate roster is set. - * - * @param rosterLifecycleStartTime the time at which the candidate roster was set - */ - public void trackCandidateRosterLifecycleStart(@NonNull final Instant rosterLifecycleStartTime) { - requireNonNull(rosterLifecycleStartTime, "rosterLifecycleStartTime must not be null"); - this.candidateRosterLifecycleStart = rosterLifecycleStartTime; - } - - /** - * The time it takes to aggregate private shares from the key material. - * - * @param aggregationTime the time which it takes to compute shares from the key material - */ - public void updateAggregationTime(final long aggregationTime) { - if (aggregationTime < 0) { - log.warn("Received negative aggregation time: {}", aggregationTime); - } else { - tssSharesAggregationTime.set(aggregationTime); - } - } - /** - * Track the number of consecutive failures to generate a ledger signatures. - */ - public void updateLedgerSignatureFailures() { - ledgerSignatureFailuresCounter.increment(); - } - - /** - * @param targetRosterHash the {@link Bytes} of the candidate roster - * @return the metric which contains how many votes are registered for candidate roster - */ - public @NonNull Counter getMessagesPerCandidateRoster(@NonNull final Bytes targetRosterHash) { - requireNonNull(targetRosterHash); - return messagesPerCandidateRoster.get(targetRosterHash); - } - - /** - * @param targetRosterHash the {@link Bytes} of the candidate roster - * @return the metric which contains how many votes are registered for candidate roster - */ - public @NonNull Counter getVotesPerCandidateRoster(@NonNull final Bytes targetRosterHash) { - requireNonNull(targetRosterHash); - return votesPerCandidateRoster.get(targetRosterHash); - } - - /** - * The time it takes to get ledger signature from the time it is requested. - * - * @param time the time it takes to get ledger signature from the time it is requested - */ - public void updateLedgerSignatureTime(final long time) { - if (time < 0) { - log.warn("Received negative signature time: {}", time); - } else { - tssLedgerSignatureTime.set(time); - } - } - - /** - * @return the aggregation time from the metric - */ - @VisibleForTesting - public long getAggregationTime() { - return tssSharesAggregationTime.get(); - } - - /** - * @return the candidate roster lifecycle from the metric - */ - @VisibleForTesting - public long getCandidateRosterLifecycle() { - return tssCandidateRosterLifecycle.get(); - } - - /** - * @return the ledger signature time from the metric - */ - @VisibleForTesting - public long getTssLedgerSignatureTime() { - return tssLedgerSignatureTime.get(); - } - - @VisibleForTesting - public Counter getLedgerSignatureFailuresCounter() { - return ledgerSignatureFailuresCounter; - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/FakeFieldElement.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/FakeFieldElement.java deleted file mode 100644 index 01f2624e6a92..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/FakeFieldElement.java +++ /dev/null @@ -1,79 +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.api; - -import com.hedera.cryptography.pairings.api.Field; -import com.hedera.cryptography.pairings.api.FieldElement; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.math.BigInteger; - -public class FakeFieldElement implements FieldElement { - - private final BigInteger value; - - public FakeFieldElement(@NonNull BigInteger value) { - this.value = value; - } - - @NonNull - @Override - public Field field() { - return null; - } - - @NonNull - @Override - public FieldElement add(@NonNull final FieldElement other) { - return new FakeFieldElement(value.add(new BigInteger(other.toBytes()))); - } - - @NonNull - @Override - public FieldElement subtract(@NonNull final FieldElement other) { - return new FakeFieldElement(value.subtract(new BigInteger(other.toBytes()))); - } - - @NonNull - @Override - public FieldElement multiply(@NonNull final FieldElement other) { - return new FakeFieldElement(value.multiply(new BigInteger(other.toBytes()))); - } - - @NonNull - @Override - public FieldElement power(final long exponent) { - return new FakeFieldElement(value.pow((int) exponent)); - } - - @NonNull - @Override - public FieldElement inverse() { - return new FakeFieldElement(value); - } - - @NonNull - @Override - public BigInteger toBigInteger() { - return value; - } - - @NonNull - @Override - public byte[] toBytes() { - return new byte[0]; - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/FakeGroupElement.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/FakeGroupElement.java deleted file mode 100644 index fdde01ef673a..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/FakeGroupElement.java +++ /dev/null @@ -1,67 +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.api; - -import com.hedera.cryptography.pairings.api.FieldElement; -import com.hedera.cryptography.pairings.api.Group; -import com.hedera.cryptography.pairings.api.GroupElement; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.math.BigInteger; - -public class FakeGroupElement implements GroupElement { - public static final FakeGroupElement GENERATOR = new FakeGroupElement(BigInteger.valueOf(5L)); - private final BigInteger value; - - public FakeGroupElement(@NonNull BigInteger value) { - this.value = value; - } - - @Override - public int size() { - return value.bitLength(); - } - - @NonNull - @Override - public Group getGroup() { - return null; - } - - @NonNull - @Override - public GroupElement multiply(@NonNull final FieldElement other) { - return new FakeGroupElement(value.multiply(new BigInteger(other.toBytes()))); - } - - @NonNull - @Override - public GroupElement add(@NonNull GroupElement other) { - return new FakeGroupElement(value.add(new BigInteger(other.toBytes()))); - } - - @NonNull - @Override - public GroupElement copy() { - return new FakeGroupElement(value); - } - - @NonNull - @Override - public byte[] toBytes() { - return value.toByteArray(); - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/TssLibrary.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/TssLibrary.java deleted file mode 100644 index 639922b12b8f..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/api/TssLibrary.java +++ /dev/null @@ -1,160 +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.api; - -import com.hedera.cryptography.bls.BlsPublicKey; -import com.hedera.cryptography.bls.BlsSignature; -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; -import com.hedera.cryptography.tss.api.TssShareSignature; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; - -/** - * A Threshold Signature Scheme Library. - * Contract of TSS: - *
    - *
  • Generate TssMessages out of PrivateShares
  • - *
  • Verify TssMessages out of a ParticipantDirectory
  • - *
  • Obtain PrivateShares out of TssMessages for each owned share
  • - *
  • Aggregate PrivateShares
  • - *
  • Obtain PublicShares out of TssMessages for each share
  • - *
  • Aggregate PublicShares
  • - *
  • Sign Messages
  • - *
  • Verify Signatures
  • - *
  • Aggregate Signatures
  • - *
- */ -// Future: This intermediate interface can be deleted and use `TssService` directly in all the places needed. -public interface TssLibrary { - - /** - * Generate a {@link TssMessage} for a {@code tssParticipantDirectory}, from a random private share. - * This method can be used to bootstrap the protocol as it does not need the existence of a previous {@link - * TssPrivateShare} - * - * @param tssParticipantDirectory the participant directory that we should generate the message for - * @return a {@link TssMessage} produced out of a random share. - */ - @NonNull - TssMessage generateTssMessage(@NonNull TssParticipantDirectory tssParticipantDirectory); - - /** - * Generate a {@link TssMessage} for a {@code tssParticipantDirectory}, for the specified {@link - * TssPrivateShare}. - * - * @param tssParticipantDirectory the participant directory that we should generate the message for - * @param privateShare the secret to use for generating new keys - * @return a TssMessage for the requested share. - */ - @NonNull - TssMessage generateTssMessage( - @NonNull TssParticipantDirectory tssParticipantDirectory, @NonNull TssPrivateShare privateShare); - - /** - * Verify that a {@link TssMessage} is valid. - * - * @param participantDirectory the participant directory used to generate the message - * @param tssMessage the {@link TssMessage} to validate - * @return true if the message is valid, false otherwise - */ - boolean verifyTssMessage(@NonNull TssParticipantDirectory participantDirectory, @NonNull Bytes tssMessage); - - /** - * Compute all private shares that belongs to this participant from a threshold minimum number of {@link - * TssMessage}s. - * It is the responsibility of the caller to ensure that the list of validTssMessages meets the required - * threshold. - * - * @param participantDirectory the pending participant directory that we should generate the private share for - * @param validTssMessages the TSS messages to extract the private shares from. They must be previously - * validated. - * @return a sorted by sharedId list of private shares the current participant owns. - * @throws IllegalStateException if there aren't enough messages to meet the threshold - */ - @NonNull - List decryptPrivateShares( - @NonNull TssParticipantDirectory participantDirectory, @NonNull List validTssMessages); - - /** - * Compute all public shares for all the participants in the scheme. - * - * @param participantDirectory the participant directory that we should generate the public shares for - * @param validTssMessages the {@link TssMessage}s to extract the public shares from. They must be previously - * validated. - * @return a sorted by the sharedId list of public shares. - * @throws IllegalStateException if there aren't enough messages to meet the threshold - */ - @NonNull - List computePublicShares( - @NonNull TssParticipantDirectory participantDirectory, @NonNull List validTssMessages); - - /** - * Aggregate a threshold number of {@link TssPublicShare}s. - * It is the responsibility of the caller to ensure that the list of public shares meets the required - * threshold. - * If the threshold is not met, the public key returned by this method will be invalid. - * This method is used for two distinct purposes: - *
    - *
  • Aggregating public shares to produce the Ledger ID
  • - *
  • Aggregating public shares derived from all commitments, to produce the public key for a given - * share
  • - *
- * - * @param publicShares the public shares to aggregate - * @return the interpolated public key - */ - @NonNull - BlsPublicKey aggregatePublicShares(@NonNull List publicShares); - - /** - * Sign a message using the private share's key. - * @param privateShare the private share to sign the message with - * @param message the message to sign - * @return the signature - */ - @NonNull - TssShareSignature sign(@NonNull TssPrivateShare privateShare, @NonNull byte[] message); - - /** - * verifies a signature using the participantDirectory and the list of public shares. - * @param participantDirectory the pending share claims the TSS message was created for - * @param publicShares the public shares to verify the signature with - * @param signature the signature to verify - * @return if the signature is valid. - */ - boolean verifySignature( - @NonNull TssParticipantDirectory participantDirectory, - @NonNull List publicShares, - @NonNull TssShareSignature signature); - - /** - * Aggregate a threshold number of {@link TssShareSignature}s. - * It is the responsibility of the caller to ensure that the list of partial signatures meets the required - * threshold. If the threshold is not met, the signature returned by this method will be invalid. - * - * @param partialSignatures the list of signatures to aggregate - * @return the interpolated signature - */ - @NonNull - BlsSignature aggregateSignatures(@NonNull List partialSignatures); - - TssMessage getTssMessageFromBytes(Bytes tssMessage, TssParticipantDirectory participantDirectory); -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssHandlers.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssHandlers.java deleted file mode 100644 index 341d8baae9f2..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssHandlers.java +++ /dev/null @@ -1,24 +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.handlers; - -import edu.umd.cs.findbugs.annotations.NonNull; - -public record TssHandlers( - @NonNull TssMessageHandler tssMessageHandler, - @NonNull TssVoteHandler tssVoteHandler, - @NonNull TssShareSignatureHandler tssShareSignatureHandler) {} 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 deleted file mode 100644 index 09e13b84c340..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java +++ /dev/null @@ -1,110 +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.handlers; - -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; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.spi.workflows.HandleException; -import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.spi.workflows.PreHandleContext; -import com.hedera.node.app.spi.workflows.TransactionHandler; -import com.hedera.node.app.tss.TssCryptographyManager; -import com.hedera.node.app.tss.TssDirectoryAccessor; -import com.hedera.node.app.tss.TssMetrics; -import com.hedera.node.app.tss.stores.WritableTssStore; -import edu.umd.cs.findbugs.annotations.NonNull; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Validates and potentially responds with a vote to a {@link TssMessageTransactionBody}. - * (TSS-FUTURE) Tracked here. - */ -@Singleton -public class TssMessageHandler implements TransactionHandler { - private final TssSubmissions submissionManager; - private final TssCryptographyManager tssCryptographyManager; - private final TssMetrics tssMetrics; - private final TssDirectoryAccessor tssDirectoryAccessor; - - @Inject - public TssMessageHandler( - @NonNull final TssSubmissions submissionManager, - @NonNull final TssCryptographyManager tssCryptographyManager, - @NonNull final TssMetrics metrics, - @NonNull final TssDirectoryAccessor tssDirectoryAccessor) { - this.submissionManager = requireNonNull(submissionManager); - this.tssCryptographyManager = requireNonNull(tssCryptographyManager); - this.tssMetrics = requireNonNull(metrics); - this.tssDirectoryAccessor = requireNonNull(tssDirectoryAccessor); - } - - @Override - public void preHandle(@NonNull PreHandleContext context) throws PreCheckException { - requireNonNull(context); - } - - @Override - public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckException { - requireNonNull(txn); - } - - @Override - public void handle(@NonNull final HandleContext context) throws HandleException { - requireNonNull(context); - final var op = context.body().tssMessageOrThrow(); - final var targetRosterHash = op.targetRosterHash(); - tssMetrics.updateMessagesPerCandidateRoster(targetRosterHash); - - final var tssStore = context.storeFactory().writableStore(WritableTssStore.class); - final var messageSeqNo = tssStore.getMessagesForTarget(targetRosterHash).size(); - // Nodes vote for a threshold set of TSS messages by their position in consensus order - final var key = new TssMessageMapKey(targetRosterHash, messageSeqNo); - // Store the latest message before potentially voting - tssStore.put(key, op); - - // Obtain the directory of participants for the target roster - 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(directory, tssMessageBodies, voteBody) - .thenAccept(vote -> { - if (vote != null) { - // FUTURE: Validate the ledgerId computed is same as the current ledgerId - final var tssVote = TssVoteTransactionBody.newBuilder() - .tssVote(vote.bitSet()) - .sourceRosterHash(op.sourceRosterHash()) - .targetRosterHash(targetRosterHash) - .ledgerId(vote.ledgerId()) - .nodeSignature(vote.signature().getBytes()) - .build(); - submissionManager.submitTssVote(tssVote, context); - } - }); - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandler.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandler.java deleted file mode 100644 index fc1d2d2767e8..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandler.java +++ /dev/null @@ -1,164 +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.handlers; - -import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; -import static com.hedera.node.app.tss.handlers.TssUtils.getThresholdForTssMessages; -import static java.util.Objects.requireNonNull; - -import com.hedera.cryptography.bls.BlsSignature; -import com.hedera.cryptography.tss.api.TssShareSignature; -import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.spi.workflows.HandleException; -import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.spi.workflows.PreHandleContext; -import com.hedera.node.app.spi.workflows.TransactionHandler; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssKeysAccessor; -import com.hedera.node.app.tss.TssMetrics; -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 edu.umd.cs.findbugs.annotations.NonNull; -import java.math.BigInteger; -import java.time.Duration; -import java.time.Instant; -import java.time.InstantSource; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Handles TSS share signature transactions. - * This is yet to be implemented. - */ -@Singleton -public class TssShareSignatureHandler implements TransactionHandler { - private static final int PURGE_INTERVAL_SECS = 60; - private final TssLibrary tssLibrary; - private final TssKeysAccessor rosterKeyMaterialAccessor; - private final InstantSource instantSource; - private final Map requests = new ConcurrentHashMap<>(); - private final Map>> signatures = new ConcurrentHashMap<>(); - private Instant lastPurgeTime = Instant.EPOCH; - private TssBaseServiceImpl tssBaseService; - private final TssMetrics tssMetrics; - - @Inject - public TssShareSignatureHandler( - @NonNull final TssLibrary tssLibrary, - @NonNull final InstantSource instantSource, - @NonNull final TssKeysAccessor rosterKeyMaterialAccessor, - @NonNull final TssBaseService tssBaseService, - final TssMetrics tssMetrics) { - this.tssLibrary = tssLibrary; - this.instantSource = instantSource; - this.rosterKeyMaterialAccessor = rosterKeyMaterialAccessor; - this.tssBaseService = (TssBaseServiceImpl) tssBaseService; - this.tssMetrics = requireNonNull(tssMetrics); - } - - @Override - public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { - requireNonNull(context); - final var body = context.body().tssShareSignatureOrThrow(); - final var messageHash = body.messageHash(); - final var shareIndex = (int) body.shareIndex(); - final var rosterHash = body.rosterHash(); - - // verify if the signature is already present - final var tssSignaturesMap = signatures.computeIfAbsent(messageHash, k -> new ConcurrentHashMap<>()); - final Set tssShareSignatures = - tssSignaturesMap.computeIfAbsent(rosterHash, k -> ConcurrentHashMap.newKeySet()); - final var isPresent = tssShareSignatures.stream().anyMatch(sig -> sig.shareId() == shareIndex); - if (!isPresent) { - // For each signature not already present for this message hash, verify with - // tssLibrary and accumulate in map - validateAndAccumulateSignatures(messageHash, shareIndex, tssShareSignatures); - // If message hash now has enough signatures to aggregate, do so and notify - // tssBaseService of sign the message hash with ledger signature - if (isThresholdMet(messageHash, rosterHash)) { - final var aggregationStart = instantSource.instant(); - final BlsSignature ledgerSignature; - try { - ledgerSignature = tssLibrary.aggregateSignatures( - tssShareSignatures.stream().toList()); - } catch (Exception e) { - // TODO: Need to confirm if this is correct metric we want. - tssMetrics.updateLedgerSignatureFailures(); - return; - } - final var aggregationEnd = instantSource.instant(); - // Update the time it took to aggregate the signatures and generate ledger signature - tssMetrics.updateLedgerSignatureTime( - Duration.between(aggregationStart, aggregationEnd).toMillis()); - tssBaseService.notifySignature(messageHash.toByteArray(), ledgerSignature.toBytes()); - } - } - // Purge any expired signature requests, at most once per second - final var now = instantSource.instant(); - if (now.getEpochSecond() > lastPurgeTime.getEpochSecond()) { - lastPurgeTime = now; - Instant threshold = now.minusSeconds(PURGE_INTERVAL_SECS); - requests.entrySet().removeIf(entry -> threshold.isAfter(entry.getValue())); - } - } - - private void validateAndAccumulateSignatures( - final Bytes messageHash, final int shareIndex, final Set tssShareSignatures) { - // Future: Use non-fake signature - final var tssShareSignature = new TssShareSignature( - shareIndex, new BlsSignature(new FakeGroupElement(BigInteger.valueOf(shareIndex)), SIGNATURE_SCHEMA)); - final var isValid = tssLibrary.verifySignature( - rosterKeyMaterialAccessor.accessTssKeys().activeParticipantDirectory(), - rosterKeyMaterialAccessor.accessTssKeys().activeRosterPublicShares(), - tssShareSignature); - if (isValid) { - tssShareSignatures.add(tssShareSignature); - requests.computeIfAbsent(messageHash, k -> instantSource.instant()); - } - } - - private boolean isThresholdMet(final Bytes messageHash, final Bytes rosterHash) { - final var shares = signatures.get(messageHash); - final var getAllSignatures = shares != null ? shares.get(rosterHash) : Set.of(); - final var totalShares = rosterKeyMaterialAccessor.accessTssKeys().totalShares(); - return getAllSignatures.size() >= getThresholdForTssMessages(totalShares); - } - - @Override - public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckException { - requireNonNull(txn); - } - - @Override - public void handle(@NonNull final HandleContext context) throws HandleException { - requireNonNull(context); - } - - public Map>> getSignatures() { - return signatures; - } - - public Map getRequests() { - return requests; - } -} 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 deleted file mode 100644 index 926d67bdf23d..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssSubmissions.java +++ /dev/null @@ -1,228 +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.handlers; - -import static com.hedera.hapi.node.base.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hedera.hapi.util.HapiUtils.asTimestamp; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.base.Duration; -import com.hedera.hapi.node.base.TransactionID; -import com.hedera.hapi.node.transaction.TransactionBody; -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.spi.AppContext; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.config.data.HederaConfig; -import com.hedera.node.config.data.TssConfig; -import com.swirlds.config.api.Configuration; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@Singleton -public class TssSubmissions { - private static final Logger log = LogManager.getLogger(TssSubmissions.class); - - private static final int NANOS_TO_SKIP_ON_DUPLICATE = 13; - private static final String DUPLICATE_TRANSACTION_REASON = "" + DUPLICATE_TRANSACTION; - - /** - * The executor to use for scheduling the work of submitting transactions. - */ - private final Executor submissionExecutor; - /** - * The next offset to use for the transaction valid start time within the current {@link HandleContext}. - */ - private final AtomicInteger nextOffset = new AtomicInteger(0); - - private final AppContext appContext; - - /** - * Tracks which {@link HandleContext} we are currently submitting TSS transactions within, to - * avoid duplicate transaction ids; even if we do somehow get a duplicate, we still retry up - * to the configured limit of {@link TssConfig#distinctTxnIdsToTry()}. - */ - @Nullable - private HandleContext lastContextUsed; - - @Inject - public TssSubmissions(@NonNull final AppContext appContext, @NonNull final Executor submissionExecutor) { - this.appContext = requireNonNull(appContext); - this.submissionExecutor = requireNonNull(submissionExecutor); - } - - /** - * Attempts to submit a TSS message to the network. - * - * @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(lastUsedConsensusTime); - return submit( - b -> b.tssMessage(body), - 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 - * @return a future that completes when the vote has been submitted - */ - public CompletableFuture submitTssVote( - @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(lastUsedConsensusTime); - return submit( - b -> b.tssVote(body), - 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 lastUsedConsensusTime the last used consensus time - * @return a future that completes when the share signature has been submitted - */ - public CompletableFuture submitTssShareSignature( - @NonNull final TssShareSignatureTransactionBody body, final Instant lastUsedConsensusTime) { - requireNonNull(body); - return submit( - b -> b.tssShareSignature(body), - appContext.configSupplier().get(), - appContext.selfNodeInfoSupplier().get().accountId(), - lastUsedConsensusTime); - } - - private CompletableFuture submit( - @NonNull final Consumer spec, - @NonNull final Configuration config, - @NonNull final AccountID selfId, - @NonNull final Instant consensusNow) { - final var tssConfig = config.getConfigData(TssConfig.class); - final var hederaConfig = config.getConfigData(HederaConfig.class); - final var validDuration = new Duration(hederaConfig.transactionMaxValidDuration()); - final var validStartTime = new AtomicReference<>(consensusNow); - final var attemptsLeft = new AtomicInteger(tssConfig.timesToTrySubmission()); - return CompletableFuture.runAsync( - () -> { - var fatalFailure = false; - var failureReason = ""; - TransactionBody body; - do { - int txnIdsLeft = tssConfig.distinctTxnIdsToTry(); - do { - final var builder = builderWith(validStartTime.get(), selfId, validDuration); - spec.accept(builder); - body = builder.build(); - try { - appContext.gossip().submit(body); - return; - } catch (IllegalArgumentException iae) { - failureReason = iae.getMessage(); - if (DUPLICATE_TRANSACTION_REASON.equals(failureReason)) { - validStartTime.set(validStartTime.get().plusNanos(NANOS_TO_SKIP_ON_DUPLICATE)); - } else { - fatalFailure = true; - break; - } - } catch (IllegalStateException ise) { - failureReason = ise.getMessage(); - // There is no point to retry immediately except on a duplicate id - break; - } - } while (txnIdsLeft-- > 1); - log.warn("Failed to submit {} ({})", body, failureReason); - try { - MILLISECONDS.sleep(tssConfig.retryDelay().toMillis()); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted while waiting to retry " + body, e); - } - } while (!fatalFailure && attemptsLeft.decrementAndGet() > 0); - throw new IllegalStateException(failureReason); - }, - submissionExecutor); - } - - private Instant nextValidStartFor(@NonNull final HandleContext context) { - if (lastContextUsed != context) { - lastContextUsed = context; - nextOffset.set(0); - } else { - nextOffset.incrementAndGet(); - } - return context.consensusNow().plusNanos(nextOffset.get()); - } - - private TransactionBody.Builder builderWith( - @NonNull final Instant validStartTime, - @NonNull final AccountID selfAccountId, - @NonNull final Duration validDuration) { - return TransactionBody.newBuilder() - .nodeAccountID(selfAccountId) - .transactionValidDuration(validDuration) - .transactionID(new TransactionID(asTimestamp(validStartTime), selfAccountId, false, 0)); - } -} 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 deleted file mode 100644 index 94211209b0a0..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java +++ /dev/null @@ -1,227 +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.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; -import com.hedera.cryptography.bls.SignatureSchema; -import com.hedera.cryptography.pairings.api.Curve; -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.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 { - 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 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 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 (final var rosterEntry : roster.rosterEntries()) { - final int numSharesPerThisNode = - computedShares.get(rosterEntry.nodeId()).intValue(); - final long nodeId = rosterEntry.nodeId(); - final var encryptionKey = - requireNonNull(tssEncryptionKeyFn.apply(nodeId), "No encryption key for node" + nodeId); - builder.withParticipant(nodeId, numSharesPerThisNode, encryptionKey); - } - return builder.build(); - } - - /** - * 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 - * @return the threshold for TSS messages - */ - public static int getThresholdForTssMessages(final long totalShares) { - return (int) (totalShares + 2) / 2; - } - - /** - * 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 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(); - 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)); - 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; - } - - /** - * Get the TSS messages from the list of valid TSS Message bodies. - * - * @param validTssOps list of valid TSS message bodies - * @param tssParticipantDirectory - * @param tssLibrary - * @return list of TSS messages - */ - public static List getTssMessages( - @NonNull final List validTssOps, - @NonNull final TssParticipantDirectory tssParticipantDirectory, - @NonNull final TssLibrary tssLibrary) { - return validTssOps.stream() - .map(TssMessageTransactionBody::tssMessage) - .map(k -> tssLibrary.getTssMessageFromBytes(k, tssParticipantDirectory)) - .toList(); - } - - /** - * Compute the number of shares each node should have based on the weight of the node. - * - * @param rosterEntries the list of roster entries - * @param maxSharesPerNode the maximum number of shares per node - * @return a map of node ID to the number of shares - */ - public static Map computeNodeShares( - @NonNull final List rosterEntries, final long maxSharesPerNode) { - final var weights = new LinkedHashMap(); - rosterEntries.forEach(entry -> weights.put(entry.nodeId(), entry.weight())); - return computeSharesFromWeights(weights, maxSharesPerNode); - } - - /** - * 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 maxShares the maximum number of shares - * @return a map of node ID to the number of shares - */ - public static Map computeSharesFromWeights( - @NonNull final Map weights, final long maxShares) { - requireNonNull(weights); - final var maxWeight = - weights.values().stream().mapToLong(Long::longValue).max().orElse(0); - final var shares = new LinkedHashMap(); - weights.forEach((nodeId, weight) -> { - final var numShares = ((maxShares * weight + maxWeight - 1) / maxWeight); - shares.put(nodeId, numShares); - }); - return shares; - } - - /** - * 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 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/handlers/TssVoteHandler.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssVoteHandler.java deleted file mode 100644 index 7bb90c4bd63c..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssVoteHandler.java +++ /dev/null @@ -1,89 +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.handlers; - -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.spi.workflows.HandleException; -import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.spi.workflows.PreHandleContext; -import com.hedera.node.app.spi.workflows.TransactionHandler; -import com.hedera.node.app.tss.TssMetrics; -import com.hedera.node.app.tss.stores.WritableTssStore; -import com.swirlds.platform.state.service.ReadableRosterStore; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.InstantSource; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Validates and responds to a {@link TssVoteTransactionBody}. - *

Tracked here - */ -@Singleton -public class TssVoteHandler implements TransactionHandler { - private final TssMetrics tssMetrics; - private final InstantSource instantSource; - - @Inject - public TssVoteHandler(@NonNull final TssMetrics tssMetrics, @NonNull final InstantSource instantSource) { - this.tssMetrics = requireNonNull(tssMetrics); - this.instantSource = instantSource; - } - - @Override - public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { - requireNonNull(context); - } - - @Override - public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckException { - requireNonNull(txn); - } - - @Override - public void handle(@NonNull final HandleContext context) throws HandleException { - requireNonNull(context); - final var op = context.body().tssVoteOrThrow(); - final var tssStore = context.storeFactory().writableStore(WritableTssStore.class); - final var targetRosterHash = op.targetRosterHash(); - tssMetrics.updateVotesPerCandidateRoster(targetRosterHash); - final var key = - new TssVoteMapKey(targetRosterHash, context.creatorInfo().nodeId()); - if (tssStore.exists(key)) { - // Ignore duplicate votes - return; - } - final var rosterStore = context.storeFactory().readableStore(ReadableRosterStore.class); - final var winningVote = tssStore.anyWinningVoteFrom(op.sourceRosterHash(), targetRosterHash, rosterStore); - if (winningVote.isEmpty()) { - // The election for target roster keys is not yet resolved, so include this vote - tssStore.put(key, op); - // And check if this vote was the winning vote - final var newWinningVote = - tssStore.anyWinningVoteFrom(op.sourceRosterHash(), targetRosterHash, rosterStore); - if (newWinningVote.isPresent()) { - // Update metrics for how long it took to receive a winning vote - tssMetrics.updateCandidateRosterLifecycle(instantSource.instant()); - } - } - } -} 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 deleted file mode 100644 index 5e6e358df67a..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchema.java +++ /dev/null @@ -1,122 +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.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/V0560TssBaseSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0560TssBaseSchema.java index 04adfadf2cd2..13c24404819e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0560TssBaseSchema.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0560TssBaseSchema.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -29,6 +29,7 @@ /** * Schema for the TSS service. */ +@Deprecated(forRemoval = true, since = "0.59.0") public class V0560TssBaseSchema extends Schema { public static final String TSS_MESSAGE_MAP_KEY = "TSS_MESSAGES"; public static final String TSS_VOTE_MAP_KEY = "TSS_VOTES"; 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 index 322d5883ada4..3ab875ee57c5 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -19,7 +19,6 @@ 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; @@ -28,7 +27,8 @@ /** * Schema for the TSS service. */ -public class V0580TssBaseSchema extends Schema implements TssBaseTransplantSchema { +@Deprecated(forRemoval = true, since = "0.59.0") +public class V0580TssBaseSchema extends Schema { 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. @@ -50,9 +50,4 @@ public V0580TssBaseSchema() { 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/schemas/V059TssBaseSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V059TssBaseSchema.java new file mode 100644 index 000000000000..983dec73957e --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V059TssBaseSchema.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 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.swirlds.state.lifecycle.Schema; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; + +/** + * Schema removing the states added in {@code 0.56.0} and {@code 0.58.0} for the inexact weights TSS scheme. + */ +@Deprecated(forRemoval = true, since = "0.59.0") +public class V059TssBaseSchema extends Schema { + private static final SemanticVersion VERSION = + SemanticVersion.newBuilder().major(0).minor(59).build(); + + public V059TssBaseSchema() { + super(VERSION); + } + + @NonNull + @Override + public Set statesToRemove() { + return Set.of("TSS_MESSAGES", "TSS_VOTES", "TSS_ENCRYPTION_KEYS"); + } +} 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 deleted file mode 100644 index 933aace5fc8d..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java +++ /dev/null @@ -1,214 +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.stores; - -import static java.util.Objects.requireNonNull; -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.TssVoteMapKey; -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 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; -import java.util.stream.IntStream; - -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 - */ - record RosterKeys(@NonNull List tssMessages, @NonNull Bytes ledgerId) { - public RosterKeys { - requireNonNull(tssMessages); - requireNonNull(ledgerId); - } - } - - /** - * If available, returns the roster keys that the given source roster voted to assign to the given target roster. - * - * @param sourceRosterHash the source roster hash - * @param targetRosterHash the target roster hash - * @param rosterStore the roster store - */ - default Optional consensusRosterKeys( - @NonNull final Bytes sourceRosterHash, - @NonNull final Bytes targetRosterHash, - @NonNull final ReadableRosterStore rosterStore) { - return anyWinningVoteFrom(sourceRosterHash, targetRosterHash, rosterStore) - .map(vote -> { - final var tssMessages = getMessagesForTarget(vote.targetRosterHash()); - final var selections = BitSet.valueOf(vote.tssVote().toByteArray()); - final var selectedMessages = IntStream.range(0, tssMessages.size()) - .filter(selections::get) - .sorted() - .mapToObj(tssMessages::get) - .toList(); - return new RosterKeys(selectedMessages, vote.ledgerId()); - }); - } - - /** - * If present, returns one of the winning votes from the given source roster hash for the keys of the target roster, - * computing the given total weight and per-node weight for the source roster from the given roster store. There is - * no guarantee of ordering between multiple winning votes. - * - * @param sourceRosterHash the source roster hash - * @param targetRosterHash the target roster hash - * @param rosterStore the roster store - * @return the roster keys, if available - */ - default Optional anyWinningVoteFrom( - @NonNull final Bytes sourceRosterHash, - @NonNull final Bytes targetRosterHash, - @NonNull final ReadableRosterStore rosterStore) { - requireNonNull(sourceRosterHash); - requireNonNull(targetRosterHash); - requireNonNull(rosterStore); - final long sourceRosterWeight; - final LongUnaryOperator nodeWeightFn; - if (Bytes.EMPTY.equals(sourceRosterHash)) { - // For the genesis roster, we assume a source roster of equal size with equal unit weights - sourceRosterWeight = requireNonNull(rosterStore.get(targetRosterHash)) - .rosterEntries() - .size(); - nodeWeightFn = nodeId -> 1; - } else { - final var entries = - requireNonNull(rosterStore.get(sourceRosterHash)).rosterEntries(); - sourceRosterWeight = entries.stream().mapToLong(RosterEntry::weight).sum(); - final var weights = entries.stream().collect(toMap(RosterEntry::nodeId, RosterEntry::weight)); - nodeWeightFn = weights::get; - } - 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 sourceRosterWeightFn a function that returns the weight of a node in the source roster given its id - * @return a winning vote, if present - */ - Optional anyWinningVoteFrom( - @NonNull Bytes sourceRosterHash, - @NonNull Bytes targetRosterHash, - long sourceRosterWeight, - @NonNull LongUnaryOperator sourceRosterWeightFn); - - /** - * Get the TSS message for the given key. - * - * @param TssMessageMapKey The key to look up. - * @return The TSS message, or null if not found. - */ - TssMessageTransactionBody getMessage(@NonNull TssMessageMapKey TssMessageMapKey); - - /** - * Check if a TSS message exists for the given key. - * - * @param tssMessageMapKey The key to check. - * @return True if a TSS message exists for the given key, false otherwise. - */ - boolean exists(@NonNull TssMessageMapKey tssMessageMapKey); - - /** - * Get the TSS vote for the given key. - * - * @param tssVoteMapKey The key to look up. - * @return The TSS vote, or null if not found. - */ - 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. - * - * @param tssVoteMapKey The key to check. - * @return True if a TSS vote exists for the given key, false otherwise. - */ - boolean exists(@NonNull TssVoteMapKey tssVoteMapKey); - - /** - * 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. - */ - List getMessagesForTarget(@NonNull Bytes rosterHash); - - /** - * 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 - 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 deleted file mode 100644 index 0fb99c9ab08c..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java +++ /dev/null @@ -1,163 +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.stores; - -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.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; - -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; -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.pbj.runtime.io.buffer.Bytes; -import com.swirlds.state.spi.ReadableKVState; -import com.swirlds.state.spi.ReadableStates; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.LongUnaryOperator; - -/** - * Provides read-only access to the TSS base store. - */ -public class ReadableTssStoreImpl implements ReadableTssStore { - /** - * The underlying data storage class that holds the airdrop data. - */ - private final ReadableKVState readableTssMessageState; - - private final ReadableKVState readableTssVoteState; - - private final ReadableKVState readableTssEncryptionKeyState; - - /** - * Create a new {@link ReadableTssStoreImpl} instance. - * - * @param states The state to use. - */ - 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_KEYS_KEY); - } - - @Override - public Optional anyWinningVoteFrom( - @NonNull final Bytes sourceRosterHash, - @NonNull final Bytes targetRosterHash, - final long sourceRosterWeight, - @NonNull final LongUnaryOperator sourceRosterWeightFn) { - requireNonNull(sourceRosterHash); - requireNonNull(targetRosterHash); - requireNonNull(sourceRosterWeightFn); - return Streams.stream(readableTssVoteState.keys()) - .filter(key -> targetRosterHash.equals(key.rosterHash())) - .map(key -> new WeightedVote( - sourceRosterWeightFn.applyAsLong(key.nodeId()), requireNonNull(readableTssVoteState.get(key)))) - .filter(vote -> sourceRosterHash.equals(vote.vote().sourceRosterHash())) - .collect(groupingBy(WeightedVote::tssVote, toList())) - .values() - .stream() - .filter(weightedVotes -> hasMetThreshold( - weightedVotes.stream().mapToLong(WeightedVote::weight).sum(), sourceRosterWeight)) - .findAny() - .map(weightedVotes -> weightedVotes.getFirst().vote()); - } - - private record WeightedVote(long weight, @NonNull TssVoteTransactionBody vote) { - public WeightedVote { - requireNonNull(vote); - } - - public Bytes tssVote() { - return vote.tssVote(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public TssMessageTransactionBody getMessage(@NonNull final TssMessageMapKey tssMessageKey) { - return readableTssMessageState.get(tssMessageKey); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean exists(@NonNull final TssMessageMapKey tssMessageKey) { - return readableTssMessageState.contains(tssMessageKey); - } - - /** - * {@inheritDoc} - */ - @Override - 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} - */ - @Override - public boolean exists(@NonNull final TssVoteMapKey tssVoteKey) { - return readableTssVoteState.contains(tssVoteKey); - } - - @Override - public List getMessagesForTarget(@NonNull final Bytes rosterHash) { - requireNonNull(rosterHash); - final List tssMessages = new ArrayList<>(); - readableTssMessageState.keys().forEachRemaining(key -> { - if (key.rosterHash().equals(rosterHash)) { - tssMessages.add(readableTssMessageState.get(key)); - } - }); - return tssMessages; - } - - /** - * {@inheritDoc} - */ - @Override - public TssEncryptionKeys getTssEncryptionKeys(final long nodeID) { - return readableTssEncryptionKeyState.get( - EntityNumber.newBuilder().number(nodeID).build()); - } -} 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 deleted file mode 100644 index 5008bc30516d..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/WritableTssStore.java +++ /dev/null @@ -1,92 +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.stores; - -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.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.spi.WritableKVState; -import com.swirlds.state.spi.WritableStates; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Extends the {@link ReadableTssStoreImpl} with write access to the TSS base store. - */ -public class WritableTssStore extends ReadableTssStoreImpl { - /** - * The underlying data storage class that holds the Pending Airdrops data. - */ - private final WritableKVState tssMessageState; - - private final WritableKVState tssVoteState; - - private final WritableKVState tssEncryptionKeyState; - - 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_KEYS_KEY); - } - - public void put(@NonNull final TssMessageMapKey tssMessageMapKey, @NonNull final TssMessageTransactionBody txBody) { - requireNonNull(tssMessageMapKey); - requireNonNull(txBody); - tssMessageState.put(tssMessageMapKey, txBody); - } - - public void put(@NonNull final TssVoteMapKey tssVoteMapKey, @NonNull final TssVoteTransactionBody txBody) { - requireNonNull(tssVoteMapKey); - requireNonNull(txBody); - tssVoteState.put(tssVoteMapKey, txBody); - } - - public void put(@NonNull final EntityNumber entityNumber, @NonNull final TssEncryptionKeyTransactionBody txBody) { - requireNonNull(entityNumber); - requireNonNull(txBody); - tssEncryptionKeyState.put(entityNumber, txBody); - } - - public void remove(@NonNull final TssMessageMapKey tssMessageMapKey) { - requireNonNull(tssMessageMapKey); - tssMessageState.remove(tssMessageMapKey); - } - - public void remove(@NonNull final TssVoteMapKey tssVoteMapKey) { - requireNonNull(tssVoteMapKey); - tssVoteState.remove(tssVoteMapKey); - } - - public void remove(@NonNull final EntityNumber entityNumber) { - requireNonNull(entityNumber); - tssEncryptionKeyState.remove(entityNumber); - } - - public void clear() { - tssVoteState.keys().forEachRemaining(tssVoteState::remove); - tssMessageState.keys().forEachRemaining(tssMessageState::remove); - tssEncryptionKeyState.keys().forEachRemaining(tssEncryptionKeyState::remove); - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionDispatcher.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionDispatcher.java index 0e42ccdfebfc..8fefb69a281d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionDispatcher.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionDispatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * Copyright (C) 2022-2025 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. @@ -222,10 +222,6 @@ private TransactionHandler getHandler(@NonNull final TransactionBody txBody) { default -> throw new UnsupportedOperationException(SYSTEM_UNDELETE_WITHOUT_ID_CASE); }; - case TSS_MESSAGE -> handlers.tssMessageHandler(); - case TSS_VOTE -> handlers.tssVoteHandler(); - case TSS_SHARE_SIGNATURE -> handlers.tssShareSignatureHandler(); - default -> throw new UnsupportedOperationException(TYPE_NOT_SUPPORTED); }; } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionHandlers.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionHandlers.java index 68fec8ddf662..cf926000bfc7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionHandlers.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionHandlers.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * Copyright (C) 2022-2025 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. @@ -70,9 +70,6 @@ import com.hedera.node.app.service.token.impl.handlers.TokenUpdateHandler; import com.hedera.node.app.service.token.impl.handlers.TokenUpdateNftsHandler; import com.hedera.node.app.service.util.impl.handlers.UtilPrngHandler; -import com.hedera.node.app.tss.handlers.TssMessageHandler; -import com.hedera.node.app.tss.handlers.TssShareSignatureHandler; -import com.hedera.node.app.tss.handlers.TssVoteHandler; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -133,7 +130,4 @@ public record TransactionHandlers( @NonNull NodeUpdateHandler nodeUpdateHandler, @NonNull NodeDeleteHandler nodeDeleteHandler, @NonNull TokenClaimAirdropHandler tokenClaimAirdropHandler, - @NonNull UtilPrngHandler utilPrngHandler, - @NonNull TssMessageHandler tssMessageHandler, - @NonNull TssVoteHandler tssVoteHandler, - @NonNull TssShareSignatureHandler tssShareSignatureHandler) {} + @NonNull UtilPrngHandler utilPrngHandler) {} 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 8b9ed57511a5..418e52bbc485 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.node.app.workflows.handle; import static com.hedera.hapi.node.base.ResponseCodeEnum.BUSY; @@ -66,7 +81,6 @@ import com.hedera.node.app.store.StoreFactoryImpl; import com.hedera.node.app.store.WritableStoreFactory; import com.hedera.node.app.throttle.ThrottleServiceManager; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.workflows.OpWorkflowMetrics; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.app.workflows.handle.cache.CacheWarmer; @@ -80,7 +94,6 @@ import com.hedera.node.config.data.BlockStreamConfig; import com.hedera.node.config.data.ConsensusConfig; import com.hedera.node.config.data.SchedulingConfig; -import com.hedera.node.config.data.TssConfig; import com.hedera.node.config.types.StreamMode; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.system.InitTrigger; @@ -131,7 +144,6 @@ public class HandleWorkflow { private final List migrationStateChanges; private final UserTxnFactory userTxnFactory; private final AddressBookHelper addressBookHelper; - private final TssBaseService tssBaseService; private final ConfigProvider configProvider; private final KVStateChangeListener kvStateChangeListener; private final BoundaryStateChangeListener boundaryStateChangeListener; @@ -165,7 +177,6 @@ public HandleWorkflow( @NonNull final List migrationStateChanges, @NonNull final UserTxnFactory userTxnFactory, @NonNull final AddressBookHelper addressBookHelper, - @NonNull final TssBaseService tssBaseService, @NonNull final KVStateChangeListener kvStateChangeListener, @NonNull final BoundaryStateChangeListener boundaryStateChangeListener, @NonNull final ScheduleService scheduleService) { @@ -190,7 +201,6 @@ public HandleWorkflow( this.userTxnFactory = requireNonNull(userTxnFactory); this.configProvider = requireNonNull(configProvider); this.addressBookHelper = requireNonNull(addressBookHelper); - this.tssBaseService = requireNonNull(tssBaseService); this.kvStateChangeListener = requireNonNull(kvStateChangeListener); this.boundaryStateChangeListener = requireNonNull(boundaryStateChangeListener); this.scheduleService = requireNonNull(scheduleService); @@ -209,9 +219,6 @@ public HandleWorkflow( 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.ensureParticipantDirectoryKnown(state); - } if (streamMode != RECORDS) { blockStreamManager.startRound(round, state); blockStreamManager.writeItem(BlockItem.newBuilder() @@ -644,14 +651,11 @@ private HandleOutput executeScheduled( * @param dispatch the dispatch to manage time for */ private void advanceTimeFor(@NonNull final UserTxn userTxn, @NonNull final Dispatch dispatch) { - // WARNING: The two time-based checks below rely on the BlockStreamManager's last-handled time - // not being updated yet, so we must not call setLastHandleTime() until after them + // WARNING: The check below relies on the BlockStreamManager's last-handled time not being updated yet, + // so we must not call setLastHandleTime() until after them processStakePeriodChanges(userTxn, dispatch); if (isNextSecond(userTxn.consensusNow(), blockStreamManager.lastHandleTime())) { - // Check the tss status and manage it if necessary - final var isStakePeriodBoundary = processStakePeriodChanges(userTxn, dispatch); - tssBaseService.manageTssStatus( - userTxn.stack(), isStakePeriodBoundary, userTxn.consensusNow(), storeMetricsService); + processStakePeriodChanges(userTxn, dispatch); } blockStreamManager.setLastHandleTime(userTxn.consensusNow()); if (streamMode != BLOCKS) { @@ -789,12 +793,12 @@ 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 boolean processStakePeriodChanges(@NonNull final UserTxn userTxn, @NonNull final Dispatch dispatch) { + private void processStakePeriodChanges(@NonNull final UserTxn userTxn, @NonNull final Dispatch dispatch) { try { - return stakePeriodChanges.process( + stakePeriodChanges.process( dispatch, userTxn.stack(), userTxn.tokenContextImpl(), @@ -807,7 +811,6 @@ private boolean processStakePeriodChanges(@NonNull final UserTxn userTxn, @NonNu // 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/HandleWorkflowModule.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowModule.java index 07d2824d84e2..58462ec0ea76 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowModule.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflowModule.java @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.node.app.workflows.handle; import static com.hedera.node.app.info.DiskStartupNetworks.tryToExport; @@ -15,14 +30,11 @@ import com.hedera.node.app.service.token.impl.handlers.TokenHandlers; import com.hedera.node.app.service.util.impl.handlers.UtilHandlers; import com.hedera.node.app.state.WorkingStateAccessor; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.node.app.tss.handlers.TssHandlers; import com.hedera.node.app.workflows.dispatcher.TransactionHandlers; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.CacheConfig; 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.utility.AutoCloseableWrapper; import com.swirlds.state.State; import dagger.Module; @@ -44,12 +56,6 @@ static Supplier provideContractHandlers(@NonNull final Contrac return contractService::handlers; } - @Provides - @Singleton - static Supplier provideTssHandlers(@NonNull final TssBaseService tssBaseService) { - return tssBaseService::tssHandlers; - } - @Provides @Singleton static EthereumTransactionHandler provideEthereumTransactionHandler( @@ -63,7 +69,7 @@ static BiConsumer provideRosterExportHelper() { return (roster, path) -> { final var network = Network.newBuilder() .nodeMetadata(roster.rosterEntries().stream() - .map(entry -> new NodeMetadata(entry, null, Bytes.EMPTY)) + .map(entry -> new NodeMetadata(entry, null)) .toList()) .build(); tryToExport(network, path); @@ -100,7 +106,6 @@ static TransactionHandlers provideTransactionHandlers( @NonNull final ConsensusHandlers consensusHandlers, @NonNull final FileHandlers fileHandlers, @NonNull final Supplier contractHandlers, - @NonNull final Supplier tssHandlers, @NonNull final ScheduleHandlers scheduleHandlers, @NonNull final TokenHandlers tokenHandlers, @NonNull final UtilHandlers utilHandlers, @@ -159,9 +164,6 @@ static TransactionHandlers provideTransactionHandlers( addressBookHandlers.nodeUpdateHandler(), addressBookHandlers.nodeDeleteHandler(), tokenHandlers.tokenClaimAirdropHandler(), - utilHandlers.prngHandler(), - tssHandlers.get().tssMessageHandler(), - tssHandlers.get().tssVoteHandler(), - tssHandlers.get().tssShareSignatureHandler()); + utilHandlers.prngHandler()); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdates.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdates.java index ace59d8f9369..26cf01605fac 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdates.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdates.java @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.node.app.workflows.handle.steps; import static com.hedera.node.app.service.networkadmin.impl.schemas.V0490FreezeSchema.FREEZE_TIME_KEY; @@ -15,7 +30,6 @@ import com.hedera.node.app.service.addressbook.impl.ReadableNodeStoreImpl; import com.hedera.node.app.service.networkadmin.FreezeService; import com.hedera.node.config.data.NetworkAdminConfig; -import com.hedera.node.config.data.TssConfig; import com.swirlds.config.api.Configuration; import com.swirlds.platform.config.AddressBookConfig; import com.swirlds.platform.state.service.PlatformStateService; @@ -93,9 +107,10 @@ public void handleTxBody( final var networkAdminConfig = config.getConfigData(NetworkAdminConfig.class); // Even if using the roster lifecycle, we only set the candidate roster at PREPARE_UPGRADE if // TSS machinery is not creating candidate rosters and keying them at stake period boundaries - if (config.getConfigData(AddressBookConfig.class).useRosterLifecycle() - && !config.getConfigData(TssConfig.class).keyCandidateRoster()) { - logger.info("Creating candidate roster at PREPARE_UPGRADE since TSS is inactive"); + final var addressBookConfig = config.getConfigData(AddressBookConfig.class); + if (addressBookConfig.useRosterLifecycle() + && addressBookConfig.createCandidateRosterOnPrepareUpgrade()) { + logger.info("Creating candidate roster at PREPARE_UPGRADE"); final var nodeStore = new ReadableNodeStoreImpl(state.getReadableStates(AddressBookService.NAME)); final var rosterStore = new WritableRosterStore(state.getWritableStates(RosterService.NAME)); 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 150e932418ad..768433cf216e 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -24,27 +24,20 @@ import com.google.common.annotations.VisibleForTesting; import com.hedera.node.app.fees.ExchangeRateManager; import com.hedera.node.app.records.ReadableBlockRecordStore; -import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.service.addressbook.AddressBookService; -import com.hedera.node.app.service.addressbook.ReadableNodeStore; import com.hedera.node.app.service.addressbook.impl.WritableNodeStore; import com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUpdater; import com.hedera.node.app.service.token.records.TokenContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.store.WritableStoreFactory; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.workflows.handle.Dispatch; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.hedera.node.config.data.StakingConfig; -import com.hedera.node.config.data.TssConfig; import com.hedera.node.config.types.StreamMode; import com.swirlds.config.api.Configuration; -import com.swirlds.platform.state.service.WritableRosterStore; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.time.LocalDate; -import java.util.Objects; import java.util.function.BiConsumer; import javax.inject.Inject; import javax.inject.Singleton; @@ -65,18 +58,15 @@ public class StakePeriodChanges { private final EndOfStakingPeriodUpdater endOfStakingPeriodUpdater; private final ExchangeRateManager exchangeRateManager; - private final TssBaseService tssBaseService; private final StoreMetricsService storeMetricsService; @Inject public StakePeriodChanges( @NonNull final EndOfStakingPeriodUpdater endOfStakingPeriodUpdater, @NonNull final ExchangeRateManager exchangeRateManager, - @NonNull final TssBaseService tssBaseService, @NonNull final StoreMetricsService storeMetricsService) { this.endOfStakingPeriodUpdater = requireNonNull(endOfStakingPeriodUpdater); this.exchangeRateManager = requireNonNull(exchangeRateManager); - this.tssBaseService = requireNonNull(tssBaseService); this.storeMetricsService = requireNonNull(storeMetricsService); } @@ -137,10 +127,6 @@ public boolean process( logger.error("CATASTROPHIC failure updating end-of-day stakes", e); stack.rollbackFullStack(); } - if (config.getConfigData(TssConfig.class).keyCandidateRoster()) { - tssBaseService.regenerateKeyMaterial(stack); - startKeyingCandidateRoster(dispatch.handleContext(), newWritableRosterStore(stack, config)); - } } return !isGenesis && isStakePeriodBoundary; } @@ -186,24 +172,6 @@ public static boolean isNextStakingPeriod( } } - private void startKeyingCandidateRoster( - @NonNull final HandleContext handleContext, @NonNull final WritableRosterStore rosterStore) { - final var storeFactory = handleContext.storeFactory(); - final var nodeStore = storeFactory.readableStore(ReadableNodeStore.class); - final var roster = nodeStore.snapshotOfFutureRoster(); - if (!Objects.equals(roster, rosterStore.getCandidateRoster()) - && !Objects.equals(roster, rosterStore.getActiveRoster())) { - rosterStore.putCandidateRoster(roster); - tssBaseService.setCandidateRoster(roster, handleContext); - } - } - - private WritableRosterStore newWritableRosterStore( - @NonNull final SavepointStackImpl stack, @NonNull final Configuration config) { - final var writableFactory = new WritableStoreFactory(stack, RosterService.NAME, config, storeMetricsService); - return writableFactory.getStore(WritableRosterStore.class); - } - private WritableNodeStore newWritableNodeStore( @NonNull final SavepointStackImpl stack, @NonNull final Configuration config) { final var writableFactory = diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java index 87545e6cb95f..ef714b92b313 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -29,7 +29,6 @@ import com.hedera.node.app.state.HederaStateInjectionModule; import com.hedera.node.app.throttle.ThrottleServiceManager; import com.hedera.node.app.throttle.ThrottleServiceModule; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.workflows.FacilityInitModule; import com.hedera.node.app.workflows.handle.DispatchProcessor; import com.hedera.node.app.workflows.handle.HandleWorkflowModule; @@ -63,9 +62,6 @@ public interface ExecutorComponent { @Component.Builder interface Builder { - @BindsInstance - Builder tssBaseService(TssBaseService tssBaseService); - @BindsInstance Builder fileServiceImpl(FileServiceImpl fileService); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java index 091585f8f237..a7c589a8aae3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -34,8 +34,6 @@ import com.hedera.node.app.state.recordcache.LegacyListRecordSource; import com.hedera.node.app.throttle.AppThrottleFactory; import com.hedera.node.app.throttle.ThrottleAccumulator; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssLibraryImpl; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.CryptographyHolder; @@ -49,7 +47,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -121,20 +118,12 @@ private ExecutorComponent newExecutorComponent( () -> state, () -> componentRef.get().throttleServiceManager().activeThrottleDefinitionsOrThrow(), ThrottleAccumulator::new)); - final var tssBaseService = new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), - ForkJoinPool.commonPool(), - NO_OP_METRICS); final var contractService = new ContractServiceImpl(appContext, NOOP_VERIFICATION_STRATEGIES, tracerBinding); final var fileService = new FileServiceImpl(); final var scheduleService = new ScheduleServiceImpl(); final var component = DaggerExecutorComponent.builder() .configProviderImpl(configProvider) .bootstrapConfigProviderImpl(bootstrapConfigProvider) - .tssBaseService(tssBaseService) .fileServiceImpl(fileService) .contractServiceImpl(contractService) .scheduleServiceImpl(scheduleService) 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 b22627bdf999..8b618d827ca5 100644 --- a/hedera-node/hedera-app/src/main/java/module-info.java +++ b/hedera-node/hedera-app/src/main/java/module-info.java @@ -2,9 +2,6 @@ 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.consensus.impl; @@ -55,7 +52,6 @@ requires io.netty.handler; requires io.netty.transport.classes.epoll; requires io.netty.transport; - requires java.annotation; requires org.apache.commons.lang3; requires static com.github.spotbugs.annotations; requires static com.google.auto.service; @@ -102,9 +98,6 @@ exports com.hedera.node.app.workflows.handle.metric; exports com.hedera.node.app.roster; exports com.hedera.node.app.tss; - exports com.hedera.node.app.tss.api; - exports com.hedera.node.app.tss.handlers; - exports com.hedera.node.app.tss.stores; exports com.hedera.node.app.statedumpers; exports com.hedera.node.app.workflows.handle.stack; exports com.hedera.node.app.fees.congestion; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java index e0775bfe40cc..f5463ca15421 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -40,9 +40,8 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.withSettings; import com.hedera.hapi.block.stream.BlockItem; @@ -54,12 +53,12 @@ import com.hedera.hapi.node.state.blockstream.BlockStreamInfo; import com.hedera.hapi.platform.event.EventTransaction; import com.hedera.hapi.platform.state.PlatformState; +import com.hedera.node.app.blocks.BlockHashSigner; import com.hedera.node.app.blocks.BlockItemWriter; import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.BlockStreamService; import com.hedera.node.app.blocks.InitialStateHash; import com.hedera.node.app.records.BlockRecordService; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; @@ -78,13 +77,13 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -113,7 +112,7 @@ class BlockStreamManagerImplTest { private final InitialStateHash hashInfo = new InitialStateHash(completedFuture(ZERO_BLOCK_HASH), 0); @Mock - private TssBaseService tssBaseService; + private BlockHashSigner blockHashSigner; @Mock private StateHashedNotification notification; @@ -204,10 +203,10 @@ void classifiesNonGenesisBlockOfSameVersionWithWorkDoneAsNoWork() { void canUpdateDistinguishedTimes() { given(configProvider.getConfiguration()).willReturn(new VersionedConfigImpl(DEFAULT_CONFIG, 1L)); subject = new BlockStreamManagerImpl( + blockHashSigner, () -> aWriter, ForkJoinPool.commonPool(), configProvider, - tssBaseService, boundaryStateChangeListener, hashInfo, SemanticVersion.DEFAULT); @@ -224,10 +223,10 @@ void canUpdateDistinguishedTimes() { void requiresLastHashToBeInitialized() { given(configProvider.getConfiguration()).willReturn(new VersionedConfigImpl(DEFAULT_CONFIG, 1)); subject = new BlockStreamManagerImpl( + blockHashSigner, () -> aWriter, ForkJoinPool.commonPool(), configProvider, - tssBaseService, boundaryStateChangeListener, hashInfo, SemanticVersion.DEFAULT); @@ -243,7 +242,6 @@ void startsAndEndsBlockWithSingleRoundPerBlockAsExpected() throws ParseException platformStateWith(null), aWriter); givenEndOfRoundSetup(); - final ArgumentCaptor blockHashCaptor = ArgumentCaptor.forClass(byte[].class); given(boundaryStateChangeListener.boundaryTimestampOrThrow()).willReturn(Timestamp.DEFAULT); given(round.getRoundNum()).willReturn(ROUND_NO); @@ -272,6 +270,8 @@ void startsAndEndsBlockWithSingleRoundPerBlockAsExpected() throws ParseException subject.writeItem(FAKE_STATE_CHANGES); subject.writeItem(FAKE_RECORD_FILE_ITEM); + // Immediately resolve to the expected ledger signature + given(blockHashSigner.signFuture(any())).willReturn(completedFuture(FIRST_FAKE_SIGNATURE)); // End the round subject.endRound(state, ROUND_NO); @@ -297,10 +297,6 @@ void startsAndEndsBlockWithSingleRoundPerBlockAsExpected() throws ParseException BlockRecordService.EPOCH); final var actualBlockInfo = infoRef.get(); assertEquals(expectedBlockInfo, actualBlockInfo); - verify(tssBaseService).requestLedgerSignature(blockHashCaptor.capture(), any()); - - // Provide the ledger signature to the subject - subject.accept(blockHashCaptor.getValue(), FIRST_FAKE_SIGNATURE.toByteArray()); // Assert the block proof was written final var proofItem = lastAItem.get(); @@ -338,7 +334,7 @@ void doesNotEndBlockWithMultipleRoundPerBlockIfNotModZero() { subject.endRound(state, ROUND_NO); // Assert the internal state of the subject has changed as expected and the writer has been closed - verify(tssBaseService, never()).requestLedgerSignature(any(), any()); + verifyNoInteractions(blockHashSigner); } @Test @@ -352,7 +348,6 @@ void alwaysEndsBlockOnFreezeRoundPerBlockAsExpected() throws ParseException { givenEndOfRoundSetup(); given(round.getRoundNum()).willReturn(ROUND_NO); given(boundaryStateChangeListener.boundaryTimestampOrThrow()).willReturn(Timestamp.DEFAULT); - final ArgumentCaptor blockHashCaptor = ArgumentCaptor.forClass(byte[].class); // Initialize the last (N-1) block hash subject.initLastBlockHash(FAKE_RESTART_BLOCK_HASH); @@ -377,6 +372,8 @@ void alwaysEndsBlockOnFreezeRoundPerBlockAsExpected() throws ParseException { subject.writeItem(FAKE_RECORD_FILE_ITEM); } + // Immediately resolve to the expected ledger signature + given(blockHashSigner.signFuture(any())).willReturn(completedFuture(FIRST_FAKE_SIGNATURE)); // End the round subject.endRound(state, ROUND_NO); @@ -402,10 +399,6 @@ void alwaysEndsBlockOnFreezeRoundPerBlockAsExpected() throws ParseException { BlockRecordService.EPOCH); final var actualBlockInfo = infoRef.get(); assertEquals(expectedBlockInfo, actualBlockInfo); - verify(tssBaseService).requestLedgerSignature(blockHashCaptor.capture(), any()); - - // Provide the ledger signature to the subject - subject.accept(blockHashCaptor.getValue(), FIRST_FAKE_SIGNATURE.toByteArray()); // Assert the block proof was written final var proofItem = lastAItem.get(); @@ -428,7 +421,6 @@ void supportsMultiplePendingBlocksWithIndirectProofAsExpected() throws ParseExce }) .when(bWriter) .writePbjItem(any()); - final ArgumentCaptor blockHashCaptor = ArgumentCaptor.forClass(byte[].class); given(round.getRoundNum()).willReturn(ROUND_NO); given(boundaryStateChangeListener.boundaryTimestampOrThrow()).willReturn(Timestamp.DEFAULT); @@ -442,6 +434,9 @@ void supportsMultiplePendingBlocksWithIndirectProofAsExpected() throws ParseExce subject.writeItem(FAKE_TRANSACTION_RESULT); subject.writeItem(FAKE_STATE_CHANGES); subject.writeItem(FAKE_RECORD_FILE_ITEM); + final var firstSignature = new CompletableFuture(); + final var secondSignature = new CompletableFuture(); + given(blockHashSigner.signFuture(any())).willReturn(firstSignature).willReturn(secondSignature); // End the round in block N subject.endRound(state, ROUND_NO); @@ -460,13 +455,8 @@ void supportsMultiplePendingBlocksWithIndirectProofAsExpected() throws ParseExce // End the round in block N+1 subject.endRound(state, ROUND_NO + 1); - verify(tssBaseService, times(2)).requestLedgerSignature(blockHashCaptor.capture(), any()); - final var allBlockHashes = blockHashCaptor.getAllValues(); - assertEquals(2, allBlockHashes.size()); - - // Provide the N+1 ledger signature to the subject first - subject.accept(allBlockHashes.getLast(), FIRST_FAKE_SIGNATURE.toByteArray()); - subject.accept(allBlockHashes.getFirst(), SECOND_FAKE_SIGNATURE.toByteArray()); + secondSignature.complete(FIRST_FAKE_SIGNATURE); + firstSignature.complete(SECOND_FAKE_SIGNATURE); // Assert both block proofs were written, but with the proof for N using an indirect proof final var aProofItem = lastAItem.get(); @@ -500,10 +490,10 @@ private void givenSubjectWith( .getOrCreateConfig(); given(configProvider.getConfiguration()).willReturn(new VersionedConfigImpl(config, 1L)); subject = new BlockStreamManagerImpl( + blockHashSigner, () -> writers[nextWriter.getAndIncrement()], ForkJoinPool.commonPool(), configProvider, - tssBaseService, boundaryStateChangeListener, hashInfo, SemanticVersion.DEFAULT); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java index 34b298a96c9d..94f581cb92a8 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2020-2025 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. @@ -22,7 +22,6 @@ import static com.swirlds.platform.system.address.AddressBookUtils.endpointFor; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -30,6 +29,7 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.node.app.DaggerHederaInjectionComponent; import com.hedera.node.app.HederaInjectionComponent; +import com.hedera.node.app.blocks.BlockHashSigner; import com.hedera.node.app.blocks.InitialStateHash; import com.hedera.node.app.blocks.impl.BoundaryStateChangeListener; import com.hedera.node.app.blocks.impl.KVStateChangeListener; @@ -47,11 +47,6 @@ import com.hedera.node.app.signature.impl.SignatureVerifierImpl; import com.hedera.node.app.spi.throttle.Throttle; import com.hedera.node.app.state.recordcache.RecordCacheService; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.node.app.tss.handlers.TssHandlers; -import com.hedera.node.app.tss.handlers.TssMessageHandler; -import com.hedera.node.app.tss.handlers.TssShareSignatureHandler; -import com.hedera.node.app.tss.handlers.TssVoteHandler; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -81,24 +76,15 @@ class IngestComponentTest { @Mock private Platform platform; - @Mock - private TssBaseService tssBaseService; - - @Mock - private TssMessageHandler tssMessageHandler; - - @Mock - private TssVoteHandler tssVoteHandler; - - @Mock - private TssShareSignatureHandler tssShareSignatureHandler; - @Mock private Throttle.Factory throttleFactory; @Mock private StartupNetworks startupNetworks; + @Mock + private BlockHashSigner blockHashSigner; + private HederaInjectionComponent app; private static final Metrics NO_OP_METRICS = new NoOpMetrics(); @@ -129,8 +115,6 @@ void setUp() { () -> DEFAULT_NODE_INFO, () -> NO_OP_METRICS, throttleFactory); - given(tssBaseService.tssHandlers()) - .willReturn(new TssHandlers(tssMessageHandler, tssVoteHandler, tssShareSignatureHandler)); app = DaggerHederaInjectionComponent.builder() .configProviderImpl(configProvider) .bootstrapConfigProviderImpl(new BootstrapConfigProviderImpl()) @@ -150,11 +134,11 @@ void setUp() { .kvStateChangeListener(new KVStateChangeListener()) .boundaryStateChangeListener(new BoundaryStateChangeListener()) .migrationStateChanges(List.of()) - .tssBaseService(tssBaseService) .initialStateHash(new InitialStateHash(completedFuture(Bytes.EMPTY), 0)) .networkInfo(mock(NetworkInfo.class)) .startupNetworks(startupNetworks) .throttleFactory(throttleFactory) + .blockHashSigner(blockHashSigner) .build(); final var state = new FakeState(); 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 f67f66384d77..5813c017327e 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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.info; import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; @@ -9,19 +24,13 @@ 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.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.PlatformStateService.PLATFORM_STATE_SERVICE; -import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import com.hedera.hapi.node.state.addressbook.Node; @@ -30,11 +39,6 @@ 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.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; -import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.node.app.config.BootstrapConfigProviderImpl; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.fixtures.state.FakeServiceMigrator; @@ -46,9 +50,7 @@ import com.hedera.node.app.service.addressbook.AddressBookService; import com.hedera.node.app.service.addressbook.impl.AddressBookServiceImpl; import com.hedera.node.app.spi.AppContext; -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.version.ServicesSoftwareVersion; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; @@ -62,7 +64,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.ReadableRosterStoreImpl; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.State; @@ -73,14 +74,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.time.InstantSource; -import java.util.BitSet; -import java.util.Comparator; import java.util.EnumSet; import java.util.List; -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; @@ -93,46 +89,23 @@ @ExtendWith(MockitoExtension.class) class DiskStartupNetworksTest { - private static final int FAKE_NETWORK_SIZE = 4; 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; + private static Network NETWORK; @BeforeAll static void setupAll() throws IOException, ParseException { try (final var fin = DiskStartupNetworks.class.getClassLoader().getResourceAsStream("bootstrap/network.json")) { - networkWithTssKeys = Network.JSON.parse(new ReadableStreamingData(requireNonNull(fin))); - networkWithTssKeys = networkWithTssKeys - .copyBuilder() - .tssMessages(networkWithTssKeys.tssMessages().stream() - .sorted(TSS_MESSAGE_COMPARATOR) - .toList()) - .build(); - networkWithoutTssKeys = networkWithTssKeys - .copyBuilder() - .tssMessages(emptyList()) - .ledgerId(Bytes.EMPTY) - .build(); + NETWORK = Network.JSON.parse(new ReadableStreamingData(requireNonNull(fin))); } } @Mock private ConfigProvider configProvider; - @Mock - private TssBaseService tssBaseService; - @Mock private AppContext appContext; - @Mock - private TssLibrary tssLibrary; - @Mock private StartupNetworks startupNetworks; @@ -143,7 +116,7 @@ static void setupAll() throws IOException, ParseException { @BeforeEach void setUp() { - subject = new DiskStartupNetworks(configProvider, tssBaseService); + subject = new DiskStartupNetworks(configProvider); } @Test @@ -162,18 +135,17 @@ void throwsOnMissingMigrationNetwork() { @Test void findsAvailableGenesisNetwork() throws IOException { givenConfig(); - putJsonAt(GENESIS_NETWORK_JSON, WithTssKeys.NO); + putJsonAt(GENESIS_NETWORK_JSON, WithTssState.NO); final var network = subject.genesisNetworkOrThrow(DEFAULT_CONFIG); - assertThat(network).isEqualTo(networkWithoutTssKeys); + assertThat(network).isEqualTo(NETWORK); } @Test void findsAvailableMigrationNetwork() throws IOException { givenConfig(); - givenValidTssMessages(); - putJsonAt(OVERRIDE_NETWORK_JSON, WithTssKeys.YES); + putJsonAt(OVERRIDE_NETWORK_JSON, WithTssState.YES); final var network = subject.migrationNetworkOrThrow(); - assertThat(network).isEqualTo(networkWithTssKeys); + assertThat(network).isEqualTo(NETWORK); } @Test @@ -206,7 +178,7 @@ void computesFromLegacyAddressBook() { @Test void archivesGenesisNetworks() throws IOException { givenConfig(); - putJsonAt(GENESIS_NETWORK_JSON, WithTssKeys.NO); + putJsonAt(GENESIS_NETWORK_JSON, WithTssState.NO); final var genesisJson = tempDir.resolve(GENESIS_NETWORK_JSON); assertThat(Files.exists(genesisJson)).isTrue(); @@ -222,7 +194,7 @@ void archivesGenesisNetworks() throws IOException { @Test void archivesUnscopedOverrideNetwork() throws IOException { givenConfig(); - putJsonAt(OVERRIDE_NETWORK_JSON, WithTssKeys.YES); + putJsonAt(OVERRIDE_NETWORK_JSON, WithTssState.YES); final var overrideJson = tempDir.resolve(OVERRIDE_NETWORK_JSON); assertThat(Files.exists(overrideJson)).isTrue(); @@ -238,7 +210,7 @@ void archivesUnscopedOverrideNetwork() throws IOException { void archivesScopedOverrideNetwork() throws IOException { givenConfig(); Files.createDirectory(tempDir.resolve("" + ROUND_NO)); - putJsonAt(ROUND_NO + File.separator + OVERRIDE_NETWORK_JSON, WithTssKeys.YES); + putJsonAt(ROUND_NO + File.separator + OVERRIDE_NETWORK_JSON, WithTssState.YES); final var overrideJson = tempDir.resolve(ROUND_NO + File.separator + OVERRIDE_NETWORK_JSON); assertThat(Files.exists(overrideJson)).isTrue(); @@ -254,13 +226,12 @@ void archivesScopedOverrideNetwork() throws IOException { @Test void overrideNetworkOnlyStillAvailableAtSameRound() throws IOException { givenConfig(); - putJsonAt(OVERRIDE_NETWORK_JSON, WithTssKeys.YES); - givenValidTssMessages(); + putJsonAt(OVERRIDE_NETWORK_JSON, WithTssState.YES); final var maybeOverrideNetwork = subject.overrideNetworkFor(ROUND_NO); assertThat(maybeOverrideNetwork).isPresent(); final var overrideNetwork = maybeOverrideNetwork.orElseThrow(); - assertThat(overrideNetwork).isEqualTo(networkWithTssKeys); + assertThat(overrideNetwork).isEqualTo(NETWORK); subject.setOverrideRound(ROUND_NO); final var unscopedOverrideJson = tempDir.resolve(OVERRIDE_NETWORK_JSON); @@ -271,7 +242,7 @@ void overrideNetworkOnlyStillAvailableAtSameRound() throws IOException { final var maybeRepeatedOverrideNetwork = subject.overrideNetworkFor(ROUND_NO); assertThat(maybeRepeatedOverrideNetwork).isPresent(); final var repeatedOverrideNetwork = maybeRepeatedOverrideNetwork.orElseThrow(); - assertThat(repeatedOverrideNetwork).isEqualTo(networkWithTssKeys); + assertThat(repeatedOverrideNetwork).isEqualTo(NETWORK); final var maybeOverrideNetworkAfterRound = subject.overrideNetworkFor(ROUND_NO + 1); assertThat(maybeOverrideNetworkAfterRound).isEmpty(); @@ -279,50 +250,32 @@ void overrideNetworkOnlyStillAvailableAtSameRound() throws IOException { @Test void writesExpectedStateInfo() throws IOException, ParseException { - final var state = stateContainingInfoFrom(networkWithTssKeys); + final var state = stateContainingInfoFrom(NETWORK); final var loc = tempDir.resolve("reproduced-network.json"); DiskStartupNetworks.writeNetworkInfo(state, loc, EnumSet.allOf(InfoType.class)); try (final var fin = Files.newInputStream(loc)) { - var network = Network.JSON.parse(new ReadableStreamingData(fin)); - network = network.copyBuilder() - .tssMessages(network.tssMessages().stream() - .sorted(TSS_MESSAGE_COMPARATOR) - .toList()) - .build(); - Assertions.assertThat(network).isEqualTo(networkWithTssKeys); + final var network = Network.JSON.parse(new ReadableStreamingData(fin)); + Assertions.assertThat(network).isEqualTo(NETWORK); } } - private enum WithTssKeys { + private enum WithTssState { YES, NO } - private void givenValidTssMessages() { - given(tssBaseService.ledgerIdFrom(any(), any())).willReturn(EXPECTED_LEDGER_ID); - } - - private void putJsonAt(@NonNull final String fileName, @NonNull final WithTssKeys withTssKeys) throws IOException { + private void putJsonAt(@NonNull final String fileName, @NonNull final WithTssState withTssState) + throws IOException { final var loc = tempDir.resolve(fileName); try (final var fout = Files.newOutputStream(loc)) { - Network.JSON.write( - withTssKeys == WithTssKeys.YES ? networkWithTssKeys : networkWithoutTssKeys, - new WritableStreamingData(fout)); + Network.JSON.write(NETWORK, new WritableStreamingData(fout)); } } private State stateContainingInfoFrom(@NonNull final Network network) { final var state = new FakeState(); final var servicesRegistry = new FakeServicesRegistry(); - given(appContext.gossip()).willReturn(UNAVAILABLE_GOSSIP); - given(appContext.instantSource()).willReturn(InstantSource.system()); - final var tssBaseService = new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - tssLibrary, - ForkJoinPool.commonPool(), - NO_OP_METRICS); + final var tssBaseService = new TssBaseServiceImpl(); PLATFORM_STATE_SERVICE.setAppVersionFn(ServicesSoftwareVersion::from); PLATFORM_STATE_SERVICE.setDiskAddressBook(new AddressBook()); Set.of( @@ -346,7 +299,6 @@ private State stateContainingInfoFrom(@NonNull final Network network) { NO_OP_METRICS, startupNetworks); addRosterInfo(state, network); - addTssInfo(state, network); addAddressBookInfo(state, network); return state; } @@ -374,42 +326,6 @@ private void addAddressBookInfo(@NonNull final FakeState state, @NonNull final N ((CommittableWritableStates) writableStates).commit(); } - private void addTssInfo(@NonNull final FakeState state, @NonNull final Network network) { - final var rosterStore = new ReadableRosterStoreImpl(state.getReadableStates(RosterService.NAME)); - final var writableStates = state.getWritableStates(TssBaseService.NAME); - final var sourceRosterHash = - Optional.ofNullable(rosterStore.getPreviousRosterHash()).orElse(Bytes.EMPTY); - final var targetRosterHash = requireNonNull(rosterStore.getCurrentRosterHash()); - - final var bitSet = new BitSet(); - final var thresholdTssMessages = network.tssMessages(); - final var tssMessages = writableStates.get(TSS_MESSAGE_MAP_KEY); - for (int i = 0, n = thresholdTssMessages.size(); i < n; i++) { - final var key = new TssMessageMapKey(targetRosterHash, i); - tssMessages.put(key, thresholdTssMessages.get(i)); - bitSet.set(i); - } - ((CommittableWritableStates) writableStates).commit(); - - final var tssVotes = writableStates.get(TSS_VOTE_MAP_KEY); - final var tssVote = Bytes.wrap(bitSet.toByteArray()); - for (int i = 0; i < FAKE_NETWORK_SIZE; i++) { - final var key = new TssVoteMapKey(targetRosterHash, i); - final var vote = new TssVoteTransactionBody( - sourceRosterHash, targetRosterHash, EXPECTED_LEDGER_ID, Bytes.EMPTY, tssVote); - tssVotes.put(key, vote); - } - - 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 = new TssEncryptionKeys(FAKE_ENCRYPTION_KEY, Bytes.EMPTY); - tssEncryptionKey.put(key, value); - } - - ((CommittableWritableStates) writableStates).commit(); - } - private void givenConfig() { final var config = HederaTestConfigBuilder.create() .withValue("networkAdmin.upgradeSysFilesLoc", tempDir.toString()) 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 deleted file mode 100644 index 62a2a7527509..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java +++ /dev/null @@ -1,541 +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; - -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<>(); - private final BiConsumer trackingConsumer = (a, b) -> { - receivedMessageHashes.add(a); - receivedSignatures.add(b); - latch.countDown(); - }; - private final AtomicInteger numCalls = new AtomicInteger(); - private final BiConsumer secondConsumer = (a, b) -> numCalls.incrementAndGet(); - - @Mock - private SchemaRegistry registry; - - @Mock - private AppContext.Gossip gossip; - - @Mock(strictness = Mock.Strictness.LENIENT) - private AppContext appContext; - - @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(), - tssLibrary, - ForkJoinPool.commonPool(), - metrics); - } - - @Test - void onlyRegisteredConsumerReceiveCallbacks() throws InterruptedException { - final var firstMessage = new byte[] {(byte) 0x01}; - final var secondMessage = new byte[] {(byte) 0x02}; - latch = new CountDownLatch(1); - - subject.registerLedgerSignatureConsumer(trackingConsumer); - subject.registerLedgerSignatureConsumer(secondConsumer); - - subject.requestLedgerSignature(firstMessage, Instant.ofEpochSecond(1_234_567L)); - assertTrue(latch.await(1, TimeUnit.SECONDS)); - subject.unregisterLedgerSignatureConsumer(secondConsumer); - latch = new CountDownLatch(1); - subject.requestLedgerSignature(secondMessage, Instant.ofEpochSecond(1_234_567L)); - assertTrue(latch.await(1, TimeUnit.SECONDS)); - - assertEquals(2, receivedMessageHashes.size()); - assertEquals(2, receivedSignatures.size()); - assertArrayEquals(firstMessage, receivedMessageHashes.getFirst()); - assertArrayEquals(secondMessage, receivedMessageHashes.getLast()); - assertEquals(1, numCalls.get()); - } - - @Test - void placeholderRegistersSchemas() { - final var captor = ArgumentCaptor.forClass(Schema.class); - - subject.registerSchemas(registry); - - 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); - } - - @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 deleted file mode 100644 index 49ed562f373c..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceTest.java +++ /dev/null @@ -1,210 +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; - -import static com.hedera.node.app.tss.handlers.TssShareSignatureHandlerTest.PRIVATE_KEY; -import static com.hedera.node.app.tss.handlers.TssShareSignatureHandlerTest.PUBLIC_KEY; -import static com.hedera.node.app.tss.handlers.TssShareSignatureHandlerTest.TSS_KEYS; -import static com.hedera.node.app.workflows.handle.steps.NodeStakeUpdatesTest.RosterCase.ACTIVE_ROSTER; -import static com.hedera.node.app.workflows.handle.steps.NodeStakeUpdatesTest.RosterCase.CURRENT_CANDIDATE_ROSTER; -import static com.hedera.node.app.workflows.handle.steps.NodeStakeUpdatesTest.RosterCase.ROSTER_NODE_1; -import static com.hedera.node.app.workflows.handle.steps.NodeStakeUpdatesTest.RosterCase.ROSTER_NODE_2; -import static com.hedera.node.app.workflows.handle.steps.NodeStakeUpdatesTest.RosterCase.ROSTER_NODE_3; -import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; -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.doCallRealMethod; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -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; -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.node.app.info.NodeInfoImpl; -import com.hedera.node.app.spi.AppContext; -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.ReadableTssStore; -import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.metrics.api.Metrics; -import com.swirlds.platform.state.service.ReadableRosterStore; -import com.swirlds.platform.state.service.WritableRosterStore; -import com.swirlds.state.lifecycle.info.NetworkInfo; -import java.time.Instant; -import java.time.InstantSource; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -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.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class TssBaseServiceTest { - @Mock(strictness = Mock.Strictness.LENIENT) - private HandleContext handleContext; - - @Mock(strictness = Mock.Strictness.LENIENT) - private AppContext appContext; - - @Mock(strictness = Mock.Strictness.LENIENT) - private StoreFactory storeFactory; - - @Mock - private WritableRosterStore rosterStore; - - @Mock(strictness = Mock.Strictness.LENIENT) - private ReadableRosterStore readableRosterStore; - - @Mock - private ReadableTssStore readableTssStore; - - @Mock(strictness = Mock.Strictness.LENIENT) - private TssLibrary tssLibrary; - - @Mock(strictness = Mock.Strictness.LENIENT) - private NetworkInfo networkInfo; - - private final TssMessage TSS_MESSAGE = "test"::getBytes; - - @Mock - private Executor executor; - - private TssBaseServiceImpl subject; - - private static final TssKeysAccessor.TssKeys TSS_KEYS = new TssKeysAccessor.TssKeys( - List.of(new TssPrivateShare(0, PRIVATE_KEY)), - List.of(new TssPublicShare(0, PUBLIC_KEY)), - Bytes.EMPTY, - TssParticipantDirectory.createBuilder() - .withParticipant(0, 1, PUBLIC_KEY) - .build(), - 1); - - @BeforeEach - void setUp() { - given(appContext.gossip()).willReturn(mock(AppContext.Gossip.class)); - given(appContext.instantSource()).willReturn(InstantSource.system()); - given(appContext.selfNodeInfoSupplier()).willReturn(() -> DEFAULT_NODE_INFO); - given(appContext.configSupplier()).willReturn(() -> HederaTestConfigBuilder.createConfig()); - - subject = new TssBaseServiceImpl( - appContext, - mock(ExecutorService.class), - mock(Executor.class), - tssLibrary, - executor, - mock(Metrics.class)); - given(handleContext.configuration()).willReturn(HederaTestConfigBuilder.createConfig()); - given(handleContext.networkInfo()).willReturn(networkInfo); - given(networkInfo.selfNodeInfo()) - .willReturn( - new NodeInfoImpl(1, AccountID.newBuilder().accountNum(3).build(), 0, null, null)); - given(storeFactory.readableStore(ReadableRosterStore.class)).willReturn(readableRosterStore); - given(readableRosterStore.getActiveRoster()).willReturn(Roster.DEFAULT); - subject.getTssKeysAccessor().setTssKeys(TSS_KEYS); - } - - @Test - void nameIsAsExpected() { - final var subject = mock(TssBaseService.class); - doCallRealMethod().when(subject).getServiceName(); - assertEquals(TssBaseService.NAME, subject.getServiceName()); - } - - @Test - @DisplayName("Service won't set the current candidate roster as the new candidate roster") - void doesntSetSameCandidateRoster() { - given(storeFactory.readableStore(ReadableTssStore.class)).willReturn(readableTssStore); - // 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); - verify(rosterStore, never()).putCandidateRoster(any()); - } - - @Test - @DisplayName("Service won't set the active roster as the new candidate roster") - void doesntSetActiveRosterAsCandidateRoster() { - given(storeFactory.readableStore(ReadableTssStore.class)).willReturn(readableTssStore); - - final var captor = ArgumentCaptor.forClass(Runnable.class); - final var rosterStore = mockWritableRosterStore(); - 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(TSS_MESSAGE); - given(handleContext.consensusNow()).willReturn(Instant.ofEpochSecond(1_234_567L)); - - subject.setCandidateRoster(ACTIVE_ROSTER, handleContext); - - verify(executor).execute(captor.capture()); - final var task = captor.getValue(); - task.run(); - verify(tssLibrary).generateTssMessage(any(), any()); - } - - @Test - @DisplayName("Service appropriately sets a new roster as the new candidate roster") - void setsCandidateRoster() { - final var captor = ArgumentCaptor.forClass(Runnable.class); - given(storeFactory.readableStore(ReadableTssStore.class)).willReturn(readableTssStore); - // 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(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)) - .build(); - - subject.setCandidateRoster(inputRoster, handleContext); - - verify(executor).execute(captor.capture()); - final var task = captor.getValue(); - task.run(); - verify(tssLibrary).generateTssMessage(any(), any()); - } - - private WritableRosterStore mockWritableRosterStore() { - // Mock retrieval of the roster store - given(handleContext.storeFactory()).willReturn(storeFactory); - given(storeFactory.readableStore(ReadableRosterStore.class)).willReturn(rosterStore); - given(storeFactory.writableStore(WritableRosterStore.class)).willReturn(rosterStore); - - // Mock the candidate and active rosters - lenient().when(rosterStore.getCandidateRoster()).thenReturn(CURRENT_CANDIDATE_ROSTER); - lenient().when(rosterStore.getActiveRoster()).thenReturn(ACTIVE_ROSTER); - - return rosterStore; - } -} 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 deleted file mode 100644 index b08e3ce9361f..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssCryptographyManagerTest.java +++ /dev/null @@ -1,165 +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; - -import static com.hedera.node.app.tss.handlers.TssUtils.computeNodeShares; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.hedera.cryptography.bls.BlsPublicKey; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -import com.hedera.cryptography.tss.api.TssPublicShare; -import com.hedera.hapi.node.base.AccountID; -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.info.NodeInfoImpl; -import com.hedera.node.app.spi.AppContext; -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.pbj.runtime.io.buffer.Bytes; -import com.swirlds.common.crypto.Signature; -import com.swirlds.state.lifecycle.info.NetworkInfo; -import java.time.InstantSource; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ForkJoinPool; -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) -public class TssCryptographyManagerTest { - private TssCryptographyManager subject; - - @Mock - private TssLibrary tssLibrary; - - @Mock - private TssParticipantDirectory tssParticipantDirectory; - - @Mock - private AppContext appContext; - - @Mock - private AppContext.Gossip gossip; - - @Mock(strictness = Mock.Strictness.LENIENT) - private HandleContext handleContext; - - @Mock - private StoreFactory storeFactory; - - @Mock - private TssMetrics tssMetrics; - - @Mock(strictness = Mock.Strictness.LENIENT) - private NetworkInfo networkInfo; - - @BeforeEach - void setUp() { - when(appContext.gossip()).thenReturn(gossip); - subject = new TssCryptographyManager( - tssLibrary, appContext, ForkJoinPool.commonPool(), tssMetrics, InstantSource.system()); - when(handleContext.networkInfo()).thenReturn(networkInfo); - when(networkInfo.selfNodeInfo()).thenReturn(new NodeInfoImpl(0, AccountID.DEFAULT, 0, null, null)); - } - - @Test - void testWhenVoteAlreadySubmitted() { - final var body = getTssBody(); - when(handleContext.storeFactory()).thenReturn(storeFactory); - // Simulate vote already submitted - final var result = - subject.getVoteFuture(tssParticipantDirectory, List.of(body), (mock(TssVoteTransactionBody.class))); - - assertNull(result.join()); - } - - @Test - void testWhenVoteNoVoteSubmittedAndThresholdNotMet() { - final var body = getTssBody(); - when(handleContext.storeFactory()).thenReturn(storeFactory); - final var result = subject.getVoteFuture(tssParticipantDirectory, List.of(body), null); - - assertNull(result.join()); - } - - @Test - void testWhenVoteNoVoteSubmittedAndThresholdMet() { - final var ledgerId = mock(BlsPublicKey.class); - final var mockPublicShares = List.of(new TssPublicShare(10, mock(BlsPublicKey.class))); - final var mockSignature = mock(Signature.class); - - final var body = getTssBody(); - when(handleContext.storeFactory()).thenReturn(storeFactory); - 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(tssParticipantDirectory, List.of(body), null); - assertNotNull(result.join()); - verify(gossip).sign(ledgerId.toBytes()); - } - - @Test - void testWhenMetException() { - final var body = getTssBody(); - when(handleContext.storeFactory()).thenReturn(storeFactory); - when(tssLibrary.verifyTssMessage(any(), any())).thenReturn(true); - - when(tssLibrary.computePublicShares(any(), any())).thenThrow(new RuntimeException()); - final var result = subject.getVoteFuture(tssParticipantDirectory, List.of(body), null); - - assertNull(result.join()); - verify(gossip, never()).sign(any()); - } - - @Test - void testComputeNodeShares() { - RosterEntry entry1 = new RosterEntry(1L, 100L, null, null); - RosterEntry entry2 = new RosterEntry(2L, 50L, null, null); - - Map result = computeNodeShares(List.of(entry1, entry2), 10L); - - assertEquals(2, result.size()); - assertEquals(10L, result.get(1L)); - assertEquals(5L, result.get(2L)); - } - - private TssMessageTransactionBody getTssBody() { - final Bytes targetRosterHash = Bytes.wrap("targetRoster".getBytes()); - final Bytes sourceRosterHash = Bytes.wrap("sourceRoster".getBytes()); - return TssMessageTransactionBody.newBuilder() - .tssMessage(Bytes.wrap("tssMessage".getBytes())) - .shareIndex(1) - .sourceRosterHash(sourceRosterHash) - .targetRosterHash(targetRosterHash) - .build(); - } -} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssLibraryImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssLibraryImplTest.java deleted file mode 100644 index 4adc4f0996cb..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssLibraryImplTest.java +++ /dev/null @@ -1,88 +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; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -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 com.hedera.cryptography.bls.BlsPrivateKey; -import com.hedera.cryptography.bls.SignatureSchema; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -import com.hedera.cryptography.tss.api.TssPrivateShare; -import com.hedera.cryptography.tss.api.TssPublicShare; -import com.hedera.cryptography.tss.api.TssShareSignature; -import com.hedera.node.app.spi.AppContext; -import java.security.SecureRandom; -import java.util.ArrayList; -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 TssLibraryImplTest { - private static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(new byte[] {1}); - - @Mock - private AppContext appContext; - - @Test - void sign() { - final var fakeTssLibrary = new TssLibraryImpl(appContext); - final var pairingPrivateKey = BlsPrivateKey.create(SIGNATURE_SCHEMA, new SecureRandom()); - final var privateShare = new TssPrivateShare(1, pairingPrivateKey); - - final var tssShareSignature = fakeTssLibrary.sign(privateShare, "Hello, World!".getBytes()); - - assertNotNull(tssShareSignature); - assertEquals(privateShare.shareId(), tssShareSignature.shareId()); - assertNotNull(tssShareSignature.signature()); - } - - @Test - void aggregatePublicShares() { - final var fakeTssLibrary = new TssLibraryImpl(appContext); - final var publicShares = new ArrayList(); - final var publicKeyShares = new long[] {1, 2, 3}; - for (int i = 0; i < publicKeyShares.length; i++) { - publicShares.add(new TssPublicShare( - i, - BlsPrivateKey.create(SIGNATURE_SCHEMA, new SecureRandom()).createPublicKey())); - } - - final var aggregatedPublicKey = fakeTssLibrary.aggregatePublicShares(publicShares); - - assertNotNull(aggregatedPublicKey); - } - - @Test - void verifySignature() { - final var tssLibrary = new TssLibraryImpl(appContext); - final var participantDirectory = mock(TssParticipantDirectory.class); - final var publicShares = new ArrayList(); - publicShares.add(mock(TssPublicShare.class)); - final var signature = mock(TssShareSignature.class); - given(signature.shareId()).willReturn(1); - - tssLibrary.verifySignature(participantDirectory, publicShares, signature); - verify(signature).verify(any(), any()); - } -} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssMetricsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssMetricsTest.java deleted file mode 100644 index 3909db560f1e..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssMetricsTest.java +++ /dev/null @@ -1,148 +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; - -import static com.swirlds.metrics.api.Metric.ValueType.VALUE; -import static org.assertj.core.api.Assertions.assertThat; - -import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.common.metrics.config.MetricsConfig; -import com.swirlds.common.metrics.platform.DefaultPlatformMetrics; -import com.swirlds.common.metrics.platform.MetricKeyRegistry; -import com.swirlds.common.metrics.platform.PlatformMetricsFactoryImpl; -import com.swirlds.common.platform.NodeId; -import com.swirlds.metrics.api.Metric; -import com.swirlds.metrics.api.MetricType; -import com.swirlds.metrics.api.Metrics; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Duration; -import java.time.Instant; -import java.time.InstantSource; -import java.util.EnumSet; -import java.util.concurrent.Executors; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class TssMetricsTest { - private static final long DEFAULT_NODE_ID = 3; - private final Metrics metrics = fakeMetrics(); - private TssMetrics tssMetrics; - - @BeforeEach - void setUp() { - tssMetrics = getTssMetrics(); - } - - @Test - public void aggregationTimeGetUpdated() { - final long aggregationTime = InstantSource.system().instant().getEpochSecond(); - tssMetrics.updateAggregationTime(aggregationTime); - assertThat(tssMetrics.getAggregationTime()).isEqualTo(aggregationTime); - } - - @Test - public void ledgerSignatureTimeGetsUpdated() { - final long aggregationTime = InstantSource.system().instant().getEpochSecond(); - tssMetrics.updateLedgerSignatureTime(aggregationTime); - assertThat(tssMetrics.getTssLedgerSignatureTime()).isEqualTo(aggregationTime); - } - - @Test - public void ledgerSignatureFailureGetsUpdated() { - tssMetrics.updateLedgerSignatureFailures(); - assertThat(tssMetrics.getLedgerSignatureFailuresCounter().get()).isEqualTo(1L); - tssMetrics.updateLedgerSignatureFailures(); - assertThat(tssMetrics.getLedgerSignatureFailuresCounter().get()).isEqualTo(2L); - } - - @Test - public void candidateRosterLifecycleGetUpdated() { - final Instant candidateRosterLifecycleStart = InstantSource.system().instant(); - tssMetrics.trackCandidateRosterLifecycleStart(candidateRosterLifecycleStart); - final Instant candidateRosterLifecycleEnd = - InstantSource.system().instant().plusMillis(5000); - tssMetrics.updateCandidateRosterLifecycle(candidateRosterLifecycleEnd); - - final long lifecycleDuration = Duration.between(candidateRosterLifecycleStart, candidateRosterLifecycleEnd) - .toMillis(); - assertThat(tssMetrics.getCandidateRosterLifecycle()).isEqualTo(lifecycleDuration); - } - - @Test - public void messagesPerCandidateRosterGetUpdated() { - final Bytes candidateRosterHash = Bytes.EMPTY; - - // register first occurrence of message per candidate roster - // (this should create a new Counter metric for this specific roster) - tssMetrics.updateMessagesPerCandidateRoster(candidateRosterHash); - assertThat(tssMetrics.getMessagesPerCandidateRoster(candidateRosterHash).getMetricType()) - .isEqualTo(MetricType.COUNTER); - assertThat(tssMetrics.getMessagesPerCandidateRoster(candidateRosterHash).getDataType()) - .isEqualTo(Metric.DataType.INT); - assertThat(tssMetrics.getMessagesPerCandidateRoster(candidateRosterHash).getValueTypes()) - .isEqualTo(EnumSet.of(VALUE)); - assertThat(tssMetrics.getMessagesPerCandidateRoster(candidateRosterHash).get()) - .isEqualTo(1L); - - // check whether the metric is incremented (increased by 1) - tssMetrics.updateMessagesPerCandidateRoster(candidateRosterHash); - assertThat(tssMetrics.getMessagesPerCandidateRoster(candidateRosterHash).get()) - .isEqualTo(2L); - } - - @Test - public void votesPerCandidateRosterGetUpdated() { - final Bytes candidateRosterHash = Bytes.EMPTY; - - // register first occurrence of vote per candidate roster - // (this should create a new Counter metric for this specific roster) - tssMetrics.updateVotesPerCandidateRoster(candidateRosterHash); - assertThat(tssMetrics.getVotesPerCandidateRoster(candidateRosterHash).getMetricType()) - .isEqualTo(MetricType.COUNTER); - assertThat(tssMetrics.getVotesPerCandidateRoster(candidateRosterHash).getDataType()) - .isEqualTo(Metric.DataType.INT); - assertThat(tssMetrics.getVotesPerCandidateRoster(candidateRosterHash).getValueTypes()) - .isEqualTo(EnumSet.of(VALUE)); - assertThat(tssMetrics.getVotesPerCandidateRoster(candidateRosterHash).get()) - .isEqualTo(1L); - - // check whether the metric is incremented (increased by 1) - tssMetrics.updateVotesPerCandidateRoster(candidateRosterHash); - assertThat(tssMetrics.getVotesPerCandidateRoster(candidateRosterHash).get()) - .isEqualTo(2L); - } - - private @NonNull TssMetrics getTssMetrics() { - return new TssMetrics(metrics); - } - - private static Metrics fakeMetrics() { - final MetricsConfig metricsConfig = - HederaTestConfigBuilder.createConfig().getConfigData(MetricsConfig.class); - - return new DefaultPlatformMetrics( - NodeId.of(DEFAULT_NODE_ID), - new MetricKeyRegistry(), - Executors.newSingleThreadScheduledExecutor(), - new PlatformMetricsFactoryImpl(metricsConfig), - metricsConfig); - } -} 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 deleted file mode 100644 index f5c09afaba6b..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java +++ /dev/null @@ -1,168 +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.handlers; - -import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; -import static com.hedera.node.app.tss.handlers.TssShareSignatureHandlerTest.PUBLIC_KEY; -import static org.junit.jupiter.api.Assertions.*; -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.when; - -import com.hedera.cryptography.bls.BlsPublicKey; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -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; -import com.hedera.node.app.tss.TssCryptographyManager; -import com.hedera.node.app.tss.TssCryptographyManager.Vote; -import com.hedera.node.app.tss.TssDirectoryAccessor; -import com.hedera.node.app.tss.TssKeysAccessor; -import com.hedera.node.app.tss.TssMetrics; -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; -import com.swirlds.state.lifecycle.info.NodeInfo; -import java.time.Instant; -import java.util.BitSet; -import java.util.List; -import java.util.concurrent.CompletableFuture; -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 TssMessageHandlerTest { - private static final AccountID NODE_ACCOUNT_ID = - AccountID.newBuilder().accountNum(666L).build(); - private static final Instant CONSENSUS_NOW = Instant.ofEpochSecond(1_234_567L, 890); - - @Mock - private TssSubmissions submissionManager; - - @Mock - private PreHandleContext preHandleContext; - - @Mock(strictness = Mock.Strictness.LENIENT) - private HandleContext handleContext; - - @Mock(strictness = Mock.Strictness.LENIENT) - private NodeInfo nodeInfo; - - @Mock(strictness = Mock.Strictness.LENIENT) - private NetworkInfo networkInfo; - - @Mock(strictness = Mock.Strictness.LENIENT) - private TssCryptographyManager tssCryptographyManager; - - @Mock - private BlsPublicKey pairingPublicKey; - - @Mock - private Signature signature; - - @Mock - private StoreFactory storeFactory; - - @Mock - private WritableTssStore tssStore; - - @Mock - private TssMetrics tssMetrics; - - @Mock - private TssDirectoryAccessor directoryAccessor; - - private TssMessageHandler subject; - private Vote vote; - private TssParticipantDirectory tssParticipantDirectory = TssParticipantDirectory.createBuilder() - .withParticipant(0, 1, PUBLIC_KEY) - .build(); - private final TssKeysAccessor.TssKeys TSS_KEYS = - new TssKeysAccessor.TssKeys(List.of(), List.of(), Bytes.EMPTY, tssParticipantDirectory, 1); - - @BeforeEach - void setUp() { - final var voteBitSet = new BitSet(8); - voteBitSet.set(2); - vote = new Vote(pairingPublicKey, signature, voteBitSet); - subject = new TssMessageHandler(submissionManager, tssCryptographyManager, tssMetrics, directoryAccessor); - } - - @Test - void nothingImplementedYet() { - assertDoesNotThrow(() -> subject.preHandle(preHandleContext)); - assertDoesNotThrow(() -> subject.pureChecks(getTssBody())); - } - - @Test - void submitsVoteOnHandlingMessageWhenThresholdMet() { - given(handleContext.networkInfo()).willReturn(networkInfo); - given(handleContext.consensusNow()).willReturn(CONSENSUS_NOW); - given(handleContext.configuration()).willReturn(DEFAULT_CONFIG); - given(networkInfo.selfNodeInfo()).willReturn(nodeInfo); - given(nodeInfo.accountId()).willReturn(NODE_ACCOUNT_ID); - given(nodeInfo.nodeId()).willReturn(1L); - given(handleContext.body()).willReturn(getTssBody()); - - when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssStore); - - 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()); - given(pairingPublicKey.toBytes()).willReturn("test".getBytes()); - subject.handle(handleContext); - - verify(submissionManager).submitTssVote(any(), eq(handleContext)); - } - - @Test - public void testHandleException() { - when(handleContext.body()).thenReturn(getTssBody()); - 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(TssVoteTransactionBody.class), any(HandleContext.class)); - } - - public static TransactionBody getTssBody() { - final Bytes targetRosterHash = Bytes.wrap("targetRoster".getBytes()); - final Bytes sourceRosterHash = Bytes.wrap("sourceRoster".getBytes()); - return TransactionBody.newBuilder() - .tssMessage(TssMessageTransactionBody.newBuilder() - .tssMessage(Bytes.wrap("tssMessage".getBytes())) - .shareIndex(1) - .sourceRosterHash(sourceRosterHash) - .targetRosterHash(targetRosterHash) - .build()) - .build(); - } -} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandlerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandlerTest.java deleted file mode 100644 index fbd7977c0fef..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandlerTest.java +++ /dev/null @@ -1,155 +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.handlers; - -import static java.math.BigInteger.ZERO; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -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 com.hedera.cryptography.bls.BlsPrivateKey; -import com.hedera.cryptography.bls.BlsPublicKey; -import com.hedera.cryptography.bls.BlsSignature; -import com.hedera.cryptography.bls.SignatureSchema; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -import com.hedera.cryptography.tss.api.TssPrivateShare; -import com.hedera.cryptography.tss.api.TssPublicShare; -import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.hapi.services.auxiliary.tss.TssShareSignatureTransactionBody; -import com.hedera.node.app.spi.workflows.PreCheckException; -import com.hedera.node.app.spi.workflows.PreHandleContext; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssKeysAccessor; -import com.hedera.node.app.tss.TssMetrics; -import com.hedera.node.app.tss.api.FakeFieldElement; -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.time.Instant; -import java.time.InstantSource; -import java.util.List; -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) -public class TssShareSignatureHandlerTest { - @Mock - private TssLibrary tssLibrary; - - @Mock - private TssKeysAccessor rosterKeyMaterialAccessor; - - @Mock - private InstantSource instantSource; - - @Mock - private TssBaseServiceImpl tssBaseService; - - @Mock - private PreHandleContext context; - - @Mock - private TssMetrics tssMetrics; - - private TssShareSignatureHandler handler; - - private static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(new byte[] {1}); - public static final BlsPrivateKey PRIVATE_KEY = - new BlsPrivateKey(new FakeFieldElement(BigInteger.valueOf(42L)), SIGNATURE_SCHEMA); - public static final BlsPublicKey PUBLIC_KEY = new BlsPublicKey(new FakeGroupElement(ZERO), SIGNATURE_SCHEMA); - private static final BlsSignature SIGNATURE = - new BlsSignature(new FakeGroupElement(BigInteger.valueOf(42L)), SIGNATURE_SCHEMA); - public static final TssKeysAccessor.TssKeys TSS_KEYS = new TssKeysAccessor.TssKeys( - List.of(new TssPrivateShare(0, PRIVATE_KEY)), - List.of(new TssPublicShare(0, PUBLIC_KEY)), - Bytes.EMPTY, - TssParticipantDirectory.createBuilder() - .withParticipant(0, 1, PUBLIC_KEY) - .build(), - 1); - - @BeforeEach - void setUp() { - given(rosterKeyMaterialAccessor.accessTssKeys()).willReturn(TSS_KEYS); - given(instantSource.instant()).willReturn(Instant.ofEpochSecond(1_234_567L)); - handler = new TssShareSignatureHandler( - tssLibrary, instantSource, rosterKeyMaterialAccessor, tssBaseService, tssMetrics); - } - - @Test - void testPreHandleValidSignatureAndThresholdMet() throws PreCheckException { - when(context.body()).thenReturn(mockTransactionBody()); - when(tssLibrary.verifySignature(any(), any(), any())).thenReturn(true); - when(tssLibrary.aggregateSignatures(anyList())).thenReturn(SIGNATURE); - - handler.preHandle(context); - verify(tssBaseService).notifySignature(any(), any()); - assertEquals( - 1, - handler.getSignatures() - .get(Bytes.wrap("message")) - .get(Bytes.wrap("roster")) - .size()); - assertEquals(1, handler.getRequests().size()); - } - - @Test - void testPreHandleSignatureAlreadyPresent() throws PreCheckException { - when(context.body()).thenReturn(mockTransactionBody()); - when(tssLibrary.verifySignature(any(), any(), any())).thenReturn(true); - when(tssLibrary.aggregateSignatures(anyList())).thenReturn(SIGNATURE); - - handler.preHandle(context); - verify(tssBaseService).notifySignature(any(), any()); - assertEquals( - 1, - handler.getSignatures() - .get(Bytes.wrap("message")) - .get(Bytes.wrap("roster")) - .size()); - assertEquals(1, handler.getRequests().size()); - - // Signature is already present - handler.preHandle(context); - verify(tssBaseService, times(1)).notifySignature(any(), any()); - assertEquals( - 1, - handler.getSignatures() - .get(Bytes.wrap("message")) - .get(Bytes.wrap("roster")) - .size()); - assertEquals(1, handler.getRequests().size()); - } - - private TransactionBody mockTransactionBody() { - return TransactionBody.newBuilder() - .tssShareSignature(TssShareSignatureTransactionBody.newBuilder() - .rosterHash(Bytes.wrap("roster")) - .messageHash(Bytes.wrap("message")) - .shareSignature(Bytes.wrap("signature")) - .build()) - .build(); - } -} 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 deleted file mode 100644 index d5da697204e8..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssSubmissionsTest.java +++ /dev/null @@ -1,175 +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.handlers; - -import static com.hedera.hapi.node.base.ResponseCodeEnum.DUPLICATE_TRANSACTION; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NODE_ACCOUNT; -import static com.hedera.hapi.util.HapiUtils.asTimestamp; -import static com.swirlds.platform.system.status.PlatformStatus.BEHIND; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.base.Duration; -import com.hedera.hapi.node.base.TransactionID; -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.AppContext; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.config.data.HederaConfig; -import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.swirlds.config.api.Configuration; -import com.swirlds.state.lifecycle.info.NetworkInfo; -import com.swirlds.state.lifecycle.info.NodeInfo; -import java.time.Instant; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -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 TssSubmissionsTest { - private static final int TIMES_TO_TRY_SUBMISSION = 3; - private static final int DISTINCT_TXN_IDS_TO_TRY = 2; - private static final int NANOS_TO_SKIP_ON_DUPLICATE = 13; - private static final String RETRY_DELAY = "1ms"; - private static final Instant CONSENSUS_NOW = Instant.ofEpochSecond(1_234_567L, 890); - private static final AccountID NODE_ACCOUNT_ID = - AccountID.newBuilder().accountNum(666L).build(); - private static final Configuration TEST_CONFIG = HederaTestConfigBuilder.create() - .withValue("tss.timesToTrySubmission", TIMES_TO_TRY_SUBMISSION) - .withValue("tss.distinctTxnIdsToTry", DISTINCT_TXN_IDS_TO_TRY) - .withValue("tss.retryDelay", RETRY_DELAY) - .getOrCreateConfig(); - private static final Duration DURATION = - new Duration(TEST_CONFIG.getConfigData(HederaConfig.class).transactionMaxValidDuration()); - - @Mock - private HandleContext context; - - @Mock - private NodeInfo nodeInfo; - - @Mock - private NetworkInfo networkInfo; - - @Mock - private AppContext.Gossip gossip; - - @Mock - private AppContext appContext; - - private TssSubmissions subject; - - @BeforeEach - void setUp() { - given(appContext.gossip()).willReturn(gossip); - subject = new TssSubmissions(appContext, ForkJoinPool.commonPool()); - given(context.consensusNow()).willReturn(CONSENSUS_NOW); - given(nodeInfo.accountId()).willReturn(NODE_ACCOUNT_ID); - given(appContext.configSupplier()).willReturn(() -> TEST_CONFIG); - given(appContext.selfNodeInfoSupplier()).willReturn(() -> nodeInfo); - } - - @Test - void futureResolvesOnSuccessfulSubmission() throws ExecutionException, InterruptedException, TimeoutException { - final var future = subject.submitTssMessage(TssMessageTransactionBody.DEFAULT, context); - - future.get(1, TimeUnit.SECONDS); - - verify(gossip).submit(messageSubmission(0)); - } - - @Test - void futureCompletesExceptionallyAfterRetriesExhausted() - throws ExecutionException, InterruptedException, TimeoutException { - doThrow(new IllegalStateException("" + BEHIND)).when(gossip).submit(any()); - - final var future = subject.submitTssVote(TssVoteTransactionBody.DEFAULT, context); - - future.exceptionally(t -> { - verify(gossip, times(TIMES_TO_TRY_SUBMISSION)).submit(voteSubmission(0)); - return null; - }) - .get(1, TimeUnit.SECONDS); - assertTrue(future.isCompletedExceptionally()); - } - - @Test - void immediatelyRetriesOnDuplicateIae() throws ExecutionException, InterruptedException, TimeoutException { - doThrow(new IllegalArgumentException("" + DUPLICATE_TRANSACTION)) - .when(gossip) - .submit(voteSubmission(0)); - - final var future = subject.submitTssVote(TssVoteTransactionBody.DEFAULT, context); - - future.get(1, TimeUnit.SECONDS); - - verify(gossip).submit(voteSubmission(NANOS_TO_SKIP_ON_DUPLICATE)); - } - - @Test - void failsImmediatelyOnHittingNonDuplicateIae() throws ExecutionException, InterruptedException, TimeoutException { - doThrow(new IllegalArgumentException("" + DUPLICATE_TRANSACTION)) - .when(gossip) - .submit(messageSubmission(0)); - doThrow(new IllegalArgumentException("" + INVALID_NODE_ACCOUNT)) - .when(gossip) - .submit(messageSubmission(NANOS_TO_SKIP_ON_DUPLICATE)); - - final var future = subject.submitTssMessage(TssMessageTransactionBody.DEFAULT, context); - - future.exceptionally(t -> { - for (int i = 0; i < DISTINCT_TXN_IDS_TO_TRY; i++) { - verify(gossip).submit(messageSubmission(i * NANOS_TO_SKIP_ON_DUPLICATE)); - } - return null; - }) - .get(1, TimeUnit.SECONDS); - assertTrue(future.isCompletedExceptionally()); - } - - private TransactionBody voteSubmission(final int nanoOffset) { - return builderFor(nanoOffset).tssVote(TssVoteTransactionBody.DEFAULT).build(); - } - - private TransactionBody messageSubmission(final int nanoOffset) { - return builderFor(nanoOffset) - .tssMessage(TssMessageTransactionBody.DEFAULT) - .build(); - } - - private TransactionBody.Builder builderFor(final int nanoOffset) { - return TransactionBody.newBuilder() - .nodeAccountID(NODE_ACCOUNT_ID) - .transactionValidDuration(DURATION) - .transactionID(TransactionID.newBuilder() - .accountID(NODE_ACCOUNT_ID) - .transactionValidStart(asTimestamp(CONSENSUS_NOW.plusNanos(nanoOffset))) - .build()); - } -} 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 deleted file mode 100644 index 2bd23e77a4ec..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java +++ /dev/null @@ -1,137 +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.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.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; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.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); - int maxSharesPerNode = 10; - - TssParticipantDirectory directory = TssUtils.computeParticipantDirectory( - new Roster(List.of(rosterEntry1, rosterEntry2)), maxSharesPerNode, nodeId -> FAKE_ENCRYPTION_KEY); - - assertNotNull(directory); - assertEquals((15 + 2) / 2, directory.getThreshold()); - assertEquals(15, directory.getTotalShares()); - assertEquals(15, directory.getShareIds().size()); - } - - @Test - 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 = voteForValidMessages( - List.of(body.tssMessageOrThrow()), tssParticipantDirectory, tssLibrary) - .get() - .validTssMessages(); - - assertEquals(1, validMessages.size()); - } - - @Test - 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 validMessagesForVote = - voteForValidMessages(List.of(body.tssMessageOrThrow()), tssParticipantDirectory, tssLibrary); - - assertTrue(validMessagesForVote.isEmpty()); - } - - @Test - public void testGetTssMessages() { - final var library = mock(TssLibrary.class); - final var tssMessage = mock(TssMessage.class); - RosterEntry rosterEntry1 = new RosterEntry(1L, 100L, null, null); - RosterEntry rosterEntry2 = new RosterEntry(2L, 50L, null, null); - 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, nodeId -> FAKE_ENCRYPTION_KEY); - - final var body = getTssBody(); - final var validTssOps = List.of(body.tssMessageOrThrow()); - final var tssMessages = TssUtils.getTssMessages(validTssOps, directory, library); - - assertEquals(1, tssMessages.size()); - assertThat(body.tssMessageOrThrow().tssMessage().toByteArray()) - .isEqualTo(tssMessages.get(0).toBytes()); - } - - @Test - public void testComputeNodeShares() { - RosterEntry entry1 = new RosterEntry(1L, 100L, null, null); - RosterEntry entry2 = new RosterEntry(2L, 50L, null, null); - - List entries = List.of(entry1, entry2); - long maxTssMessagesPerNode = 10L; - - final var shares = TssUtils.computeNodeShares(entries, maxTssMessagesPerNode); - - assertEquals(2, shares.size()); - assertEquals(10L, shares.get(1L)); - assertEquals(5L, shares.get(2L)); - } - - @Test - public void testComputeNodeSharesEmptyRoster() { - List entries = List.of(); - long maxTssMessagesPerNode = 10L; - - final var shares = TssUtils.computeNodeShares(entries, maxTssMessagesPerNode); - - assertTrue(shares.isEmpty()); - } -} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssVoteHandlerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssVoteHandlerTest.java deleted file mode 100644 index fa8a83d1a322..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssVoteHandlerTest.java +++ /dev/null @@ -1,128 +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.handlers; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mock.Strictness.LENIENT; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.node.transaction.TransactionBody; -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.HandleException; -import com.hedera.node.app.spi.workflows.PreHandleContext; -import com.hedera.node.app.tss.TssMetrics; -import com.hedera.node.app.tss.stores.WritableTssStore; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.platform.state.service.ReadableRosterStore; -import com.swirlds.state.lifecycle.info.NodeInfo; -import java.time.InstantSource; -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.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class TssVoteHandlerTest { - - @Mock - private PreHandleContext preHandleContext; - - @Mock(strictness = LENIENT) - private HandleContext handleContext; - - @Mock - private WritableTssStore tssBaseStore; - - @Mock - private ReadableRosterStore rosterStore; - - @Mock - private TssVoteTransactionBody tssVoteTransactionBody; - - @Mock - private TransactionBody transactionBody; - - @Mock - private StoreFactory storeFactory; - - @Mock - private TssMetrics tssMetrics; - - @Mock - private InstantSource instantSource; - - @Mock(strictness = LENIENT) - private NodeInfo nodeInfo; - - private TssVoteHandler tssVoteHandler; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - tssVoteHandler = new TssVoteHandler(tssMetrics, instantSource); - when(handleContext.creatorInfo()).thenReturn(nodeInfo); - when(nodeInfo.nodeId()).thenReturn(1L); - } - - @Test - void handleDoesNotThrowWhenValidContext() throws HandleException { - when(handleContext.body()).thenReturn(transactionBody); - when(transactionBody.tssVoteOrThrow()).thenReturn(tssVoteTransactionBody); - when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssBaseStore); - - when(tssVoteTransactionBody.targetRosterHash()).thenReturn(Bytes.EMPTY); - when(tssBaseStore.exists(any(TssVoteMapKey.class))).thenReturn(false); - - tssVoteHandler.handle(handleContext); - - verify(tssBaseStore).put(any(TssVoteMapKey.class), eq(tssVoteTransactionBody)); - } - - @Test - void handleReturnsWhenDuplicateVoteExists() throws HandleException { - when(handleContext.body()).thenReturn(transactionBody); - when(transactionBody.tssVoteOrThrow()).thenReturn(tssVoteTransactionBody); - when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssBaseStore); - when(tssVoteTransactionBody.targetRosterHash()).thenReturn(Bytes.EMPTY); - when(tssBaseStore.exists(any(TssVoteMapKey.class))).thenReturn(true); - - tssVoteHandler.handle(handleContext); - - verify(tssBaseStore, never()).put(any(TssVoteMapKey.class), eq(tssVoteTransactionBody)); - } - - @Test - void preHandleDoesNotThrowWhenContextIsValid() { - assertDoesNotThrow(() -> tssVoteHandler.preHandle(preHandleContext)); - } - - @Test - void pureChecksDoesNotThrowWhenTransactionBodyIsValid() { - assertDoesNotThrow(() -> tssVoteHandler.pureChecks(transactionBody)); - } -} 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 deleted file mode 100644 index e0ed2cff734f..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchemaTest.java +++ /dev/null @@ -1,175 +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.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 deleted file mode 100644 index 1bd0898c44b1..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java +++ /dev/null @@ -1,305 +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.stores; - -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.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; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -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.TssVoteMapKey; -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.ReadableStates; -import java.util.BitSet; -import java.util.List; -import java.util.Optional; -import java.util.function.LongUnaryOperator; -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.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class ReadableTssStoreTest { - - @Mock - private ReadableKVState readableTssMessageState; - - @Mock - private ReadableKVState readableTssVoteState; - - @Mock - private ReadableKVState readableTssEncryptionKeyState; - - @Mock - private ReadableStates states; - - private ReadableTssStoreImpl tssStore; - - 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 long SOURCE_WEIGHT = 9L; - 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(); - - @Mock - private ReadableRosterStore rosterStore; - - @Mock - private ReadableTssStore subject; - - private final ArgumentCaptor nodeWeightCaptor = ArgumentCaptor.forClass(LongUnaryOperator.class); - - @BeforeEach - void setUp() { - when(states.get(TSS_MESSAGE_MAP_KEY)) - .thenReturn(readableTssMessageState); - when(states.get(TSS_VOTE_MAP_KEY)) - .thenReturn(readableTssVoteState); - when(states.get(TSS_ENCRYPTION_KEYS_KEY)) - .thenReturn(readableTssEncryptionKeyState); - - tssStore = new ReadableTssStoreImpl(states); - } - - @Test - void assumesSourceRosterIsEquallySizedAndEquallyWeightedWhenMissingFromStore() { - doCallRealMethod().when(subject).anyWinningVoteFrom(any(), any(), any()); - given(rosterStore.get(TARGET_HASH)).willReturn(TARGET_ROSTER); - - subject.anyWinningVoteFrom(Bytes.EMPTY, TARGET_HASH, rosterStore); - - verify(subject) - .anyWinningVoteFrom( - eq(Bytes.EMPTY), - eq(TARGET_HASH), - eq((long) TARGET_ROSTER.rosterEntries().size()), - nodeWeightCaptor.capture()); - - final var nodeWeight = nodeWeightCaptor.getValue(); - TARGET_ROSTER.rosterEntries().forEach(entry -> assertEquals(1L, nodeWeight.applyAsLong(entry.nodeId()))); - } - - @Test - void usesSourceRosterWeightsWhenPresentInStore() { - doCallRealMethod().when(subject).anyWinningVoteFrom(any(), any(), any()); - given(rosterStore.get(SOURCE_HASH)).willReturn(SOURCE_ROSTER); - - subject.anyWinningVoteFrom(SOURCE_HASH, TARGET_HASH, rosterStore); - - verify(subject) - .anyWinningVoteFrom(eq(SOURCE_HASH), eq(TARGET_HASH), eq(SOURCE_WEIGHT), nodeWeightCaptor.capture()); - - final var nodeWeight = nodeWeightCaptor.getValue(); - SOURCE_ROSTER - .rosterEntries() - .forEach(entry -> assertEquals(entry.weight(), nodeWeight.applyAsLong(entry.nodeId()))); - } - - @Test - void noRosterKeysWithoutWinningVote() { - doCallRealMethod().when(subject).consensusRosterKeys(SOURCE_HASH, TARGET_HASH, rosterStore); - - assertTrue(subject.consensusRosterKeys(SOURCE_HASH, TARGET_HASH, rosterStore) - .isEmpty()); - } - - @Test - void includesExpectedRosterKeys() { - final var messages = IntStream.range(0, 4) - .mapToObj(i -> - new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, i * 2L + 1, Bytes.wrap("MESSAGE" + i))) - .toList(); - doCallRealMethod().when(subject).consensusRosterKeys(SOURCE_HASH, TARGET_HASH, rosterStore); - final var ledgerId = Bytes.wrap("LEDGER_ID"); - final var tssVote = new BitSet(); - tssVote.set(1); - tssVote.set(3); - final var winningVote = new TssVoteTransactionBody( - SOURCE_HASH, TARGET_HASH, ledgerId, Bytes.EMPTY, Bytes.wrap(tssVote.toByteArray())); - given(subject.anyWinningVoteFrom(SOURCE_HASH, TARGET_HASH, rosterStore)).willReturn(Optional.of(winningVote)); - given(subject.getMessagesForTarget(TARGET_HASH)).willReturn(messages); - - final var maybeRosterKeys = subject.consensusRosterKeys(SOURCE_HASH, TARGET_HASH, rosterStore); - assertThat(maybeRosterKeys).isPresent(); - final var rosterKeys = maybeRosterKeys.orElseThrow(); - assertThat(rosterKeys.tssMessages()).containsExactly(messages.get(1), messages.get(3)); - assertThat(rosterKeys.ledgerId()).isEqualTo(ledgerId); - } - - @Test - void testGetMessage() { - TssMessageMapKey key = TssMessageMapKey.DEFAULT; - TssMessageTransactionBody message = TssMessageTransactionBody.DEFAULT; - when(readableTssMessageState.get(key)).thenReturn(message); - - TssMessageTransactionBody result = tssStore.getMessage(key); - assertEquals(message, result); - } - - @Test - void testExistsMessage() { - TssMessageMapKey key = TssMessageMapKey.DEFAULT; - when(readableTssMessageState.contains(key)).thenReturn(true); - - assertTrue(tssStore.exists(key)); - } - - @Test - void testGetVote() { - TssVoteMapKey key = TssVoteMapKey.DEFAULT; - TssVoteTransactionBody vote = TssVoteTransactionBody.DEFAULT; - when(readableTssVoteState.get(key)).thenReturn(vote); - - TssVoteTransactionBody result = tssStore.getVote(key); - assertEquals(vote, result); - } - - @Test - void testExistsVote() { - TssVoteMapKey key = TssVoteMapKey.DEFAULT; - when(readableTssVoteState.contains(key)).thenReturn(true); - - 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()); - TssMessageMapKey key = mock(TssMessageMapKey.class); - TssMessageTransactionBody message = mock(TssMessageTransactionBody.class); - when(key.rosterHash()).thenReturn(rosterHash); - when(readableTssMessageState.keys()).thenReturn(singletonList(key).iterator()); - when(readableTssMessageState.get(key)).thenReturn(message); - - List result = tssStore.getMessagesForTarget(rosterHash); - assertEquals(1, result.size()); - assertEquals(message, result.get(0)); - } - - @Test - void testGetTssEncryptionKey() { - long nodeID = 123L; - EntityNumber entityNumber = new EntityNumber(nodeID); - when(readableTssEncryptionKeyState.get(entityNumber)).thenReturn(TssEncryptionKeys.DEFAULT); - final var keys = tssStore.getTssEncryptionKeys(nodeID); - assertSame(TssEncryptionKeys.DEFAULT, keys); - } - - @Test - void testAnyWinningVoteFrom() { - Bytes sourceRosterHash = Bytes.wrap("sourceHash".getBytes()); - Bytes targetRosterHash = Bytes.wrap("targetHash".getBytes()); - TssVoteMapKey key = mock(TssVoteMapKey.class); - TssVoteTransactionBody vote = mock(TssVoteTransactionBody.class); - when(key.rosterHash()).thenReturn(targetRosterHash); - when(vote.sourceRosterHash()).thenReturn(sourceRosterHash); - when(readableTssVoteState.keys()).thenReturn(singletonList(key).iterator()); - when(readableTssVoteState.get(key)).thenReturn(vote); - when(vote.tssVote()).thenReturn(Bytes.wrap("vote".getBytes())); - - LongUnaryOperator weightFn = nodeId -> 10L; - Optional result = - 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/tss/stores/WritableTssStoreTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/WritableTssStoreTest.java deleted file mode 100644 index a71d546212a0..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/WritableTssStoreTest.java +++ /dev/null @@ -1,124 +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.stores; - -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 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.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.WritableStates; -import java.util.Iterator; -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 WritableTssStoreTest { - @Mock - private WritableKVState tssMessageState; - - @Mock - private WritableKVState tssVoteState; - - @Mock - private WritableKVState tssEncryptionKeyState; - - @Mock - private WritableStates states; - - private WritableTssStore tssStore; - - @BeforeEach - void setUp() { - when(states.get(TSS_MESSAGE_MAP_KEY)) - .thenReturn(tssMessageState); - when(states.get(TSS_VOTE_MAP_KEY)) - .thenReturn(tssVoteState); - when(states.get(TSS_ENCRYPTION_KEYS_KEY)) - .thenReturn(tssEncryptionKeyState); - - tssStore = new WritableTssStore(states); - } - - @Test - void testPutTssMessage() { - TssMessageMapKey key = TssMessageMapKey.DEFAULT; - TssMessageTransactionBody body = TssMessageTransactionBody.DEFAULT; - tssStore.put(key, body); - verify(tssMessageState).put(key, body); - } - - @Test - void testPutTssVote() { - TssVoteMapKey key = TssVoteMapKey.DEFAULT; - TssVoteTransactionBody body = TssVoteTransactionBody.DEFAULT; - tssStore.put(key, body); - verify(tssVoteState).put(key, body); - } - - @Test - void testPutEncryptionKey() { - EntityNumber entityNumber = new EntityNumber(1); - TssEncryptionKeyTransactionBody body = TssEncryptionKeyTransactionBody.DEFAULT; - tssStore.put(entityNumber, body); - verify(tssEncryptionKeyState).put(entityNumber, body); - } - - @Test - void testRemoveTssMessage() { - TssMessageMapKey key = TssMessageMapKey.DEFAULT; - tssStore.remove(key); - verify(tssMessageState).remove(key); - } - - @Test - void testRemoveTssVote() { - TssVoteMapKey key = TssVoteMapKey.DEFAULT; - tssStore.remove(key); - verify(tssVoteState).remove(key); - } - - @Test - void testRemoveEncryptionKey() { - EntityNumber entityNumber = new EntityNumber(1); - tssStore.remove(entityNumber); - verify(tssEncryptionKeyState).remove(entityNumber); - } - - @Test - void testClear() { - when(tssVoteState.keys()).thenReturn(mock(Iterator.class)); - when(tssMessageState.keys()).thenReturn(mock(Iterator.class)); - when(tssEncryptionKeyState.keys()).thenReturn(mock(Iterator.class)); - - tssStore.clear(); - - verify(tssVoteState).keys(); - verify(tssMessageState).keys(); - verify(tssEncryptionKeyState).keys(); - } -} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowModuleTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowModuleTest.java index 2e9213c0388f..61c3b35c46f2 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowModuleTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowModuleTest.java @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.node.app.workflows.handle; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; @@ -65,10 +80,6 @@ import com.hedera.node.app.service.token.impl.handlers.TokenUpdateHandler; import com.hedera.node.app.service.util.impl.handlers.UtilHandlers; import com.hedera.node.app.service.util.impl.handlers.UtilPrngHandler; -import com.hedera.node.app.tss.handlers.TssHandlers; -import com.hedera.node.app.tss.handlers.TssMessageHandler; -import com.hedera.node.app.tss.handlers.TssShareSignatureHandler; -import com.hedera.node.app.tss.handlers.TssVoteHandler; import com.hedera.node.app.workflows.dispatcher.TransactionHandlers; import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.List; @@ -251,15 +262,6 @@ class HandleWorkflowModuleTest { @Mock private UtilPrngHandler utilPrngHandler; - @Mock - private TssMessageHandler tssMessageHandler; - - @Mock - private TssVoteHandler tssVoteHandler; - - @Mock - private TssShareSignatureHandler tssShareSignatureHandler; - @TempDir java.nio.file.Path tempDir; @@ -331,7 +333,6 @@ void usesComponentsToGetHandlers() { consensusHandlers, fileHandlers, () -> contractHandlers, - () -> new TssHandlers(tssMessageHandler, tssVoteHandler, tssShareSignatureHandler), scheduleHandlers, tokenHandlers, utilHandlers, diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java index 03670c97601b..94153992bb85 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -42,7 +42,6 @@ import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.state.HederaRecordCache; import com.hedera.node.app.throttle.ThrottleServiceManager; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.workflows.OpWorkflowMetrics; import com.hedera.node.app.workflows.handle.cache.CacheWarmer; import com.hedera.node.app.workflows.handle.record.SystemSetup; @@ -145,9 +144,6 @@ class HandleWorkflowTest { @Mock private UserTxnFactory userTxnFactory; - @Mock - private TssBaseService tssBaseService; - private HandleWorkflow subject; @BeforeEach @@ -224,7 +220,6 @@ private void givenSubjectWith( migrationStateChanges, userTxnFactory, new AddressBookHelper(), - tssBaseService, kvStateChangeListener, boundaryStateChangeListener, scheduleService); 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 b9e2c67cc19a..ee9e2ee803c5 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -24,58 +24,34 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; -import com.hedera.hapi.node.base.ServiceEndpoint; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.state.addressbook.Node; import com.hedera.hapi.node.state.blockrecords.BlockInfo; import com.hedera.hapi.node.state.common.EntityNumber; -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.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; import com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUpdater; import com.hedera.node.app.service.token.records.TokenContext; import com.hedera.node.app.spi.metrics.StoreMetricsService; -import com.hedera.node.app.spi.store.StoreFactory; -import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.workflows.handle.Dispatch; import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl; import com.hedera.node.config.data.StakingConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.hedera.node.config.types.StreamMode; -import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; -import com.swirlds.platform.roster.RosterUtils; import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; -import com.swirlds.state.test.fixtures.MapWritableKVState; import java.time.Duration; import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import java.util.Map; import java.util.function.BiConsumer; -import java.util.stream.Collectors; import org.assertj.core.api.Assertions; 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; @@ -97,9 +73,6 @@ public class NodeStakeUpdatesTest { @Mock private ReadableBlockRecordStore blockStore; - @Mock - private TssBaseService tssBaseService; - @Mock private SavepointStackImpl stack; @@ -109,18 +82,9 @@ public class NodeStakeUpdatesTest { @Mock private WritableStates writableStates; - @Mock - private HandleContext handleContext; - - @Mock - private StoreFactory storeFactory; - @Mock private StoreMetricsService storeMetricsService; - @Mock - private WritableSingletonState rosterState; - @Mock private WritableKVState nodesState; @@ -130,25 +94,7 @@ public class NodeStakeUpdatesTest { void setUp() { given(context.readableStore(ReadableBlockRecordStore.class)).willReturn(blockStore); - subject = new StakePeriodChanges( - stakingPeriodCalculator, exchangeRateManager, tssBaseService, storeMetricsService); - } - - @SuppressWarnings("DataFlowIssue") - @Test - void nullArgConstructor() { - Assertions.assertThatThrownBy( - () -> new StakePeriodChanges(null, exchangeRateManager, tssBaseService, storeMetricsService)) - .isInstanceOf(NullPointerException.class); - Assertions.assertThatThrownBy(() -> - new StakePeriodChanges(stakingPeriodCalculator, null, tssBaseService, storeMetricsService)) - .isInstanceOf(NullPointerException.class); - Assertions.assertThatThrownBy(() -> - new StakePeriodChanges(stakingPeriodCalculator, exchangeRateManager, null, storeMetricsService)) - .isInstanceOf(NullPointerException.class); - Assertions.assertThatThrownBy(() -> - new StakePeriodChanges(stakingPeriodCalculator, exchangeRateManager, tssBaseService, null)) - .isInstanceOf(NullPointerException.class); + subject = new StakePeriodChanges(stakingPeriodCalculator, exchangeRateManager, storeMetricsService); } @Test @@ -331,119 +277,6 @@ void isNextStakingPeriodNowCustomStakingPeriodIsLater() { Assertions.assertThat(result).isTrue(); } - @Test - void stakingPeriodDoesntSetCandidateRosterForDisabledFlag() { - // Simulate staking information - given(context.configuration()).willReturn(newConfig(990, false)); - given(blockStore.getLastBlockInfo()) - .willReturn(BlockInfo.newBuilder() - .consTimeOfLastHandledTxn(new Timestamp(CONSENSUS_TIME_1234567.getEpochSecond(), 0)) - .build()); - given(context.consensusTime()).willReturn(CONSENSUS_TIME_1234567.plus(Duration.ofDays(2))); - - subject.process(dispatch, stack, context, StreamMode.RECORDS, false, Instant.EPOCH); - verifyNoInteractions(tssBaseService); - } - - @Test - @DisplayName("Service won't set the current candidate roster as the new candidate roster") - void doesntSetSameCandidateRoster() { - // Simulate staking information, - given(blockStore.getLastBlockInfo()) - .willReturn(BlockInfo.newBuilder() - .consTimeOfLastHandledTxn(new Timestamp(CONSENSUS_TIME_1234567.getEpochSecond(), 0)) - .build()); - given(context.consensusTime()).willReturn(CONSENSUS_TIME_1234567.plus(Duration.ofDays(2))); - - // Simulate disabled `keyCandidateRoster` property - given(context.configuration()).willReturn(newConfig(990, false)); - - subject.process(dispatch, stack, context, StreamMode.RECORDS, false, Instant.EPOCH); - verify(tssBaseService, never()).setCandidateRoster(any(), any()); - } - - @Test - @DisplayName("Service won't set the active roster as the new candidate roster") - void doesntSetActiveRosterAsCandidateRoster() { - // Simulate staking information - given(blockStore.getLastBlockInfo()) - .willReturn(BlockInfo.newBuilder() - .consTimeOfLastHandledTxn(new Timestamp(CONSENSUS_TIME_1234567.getEpochSecond(), 0)) - .build()); - given(context.consensusTime()).willReturn(CONSENSUS_TIME_1234567.plus(Duration.ofDays(2))); - - // Enable keyCandidateRoster - given(context.configuration()).willReturn(newConfig(DEFAULT_STAKING_PERIOD_MINS, true)); - - // Simulate the same address book input as the current candidate and active rosters - final var nodeStore = simulateNodes(RosterCase.NODE_1, RosterCase.NODE_2, RosterCase.NODE_3, RosterCase.NODE_4); - given(dispatch.handleContext()).willReturn(handleContext); - given(handleContext.storeFactory()).willReturn(storeFactory); - given(storeFactory.readableStore(ReadableNodeStore.class)).willReturn(nodeStore); - given(stack.getWritableStates(notNull())).willReturn(writableStates); - simulateCandidateAndActiveRosters(); - - // Attempt to set the (equivalent) active roster as the new candidate roster - subject.process(dispatch, stack, context, StreamMode.RECORDS, false, Instant.EPOCH); - verify(tssBaseService, never()).setCandidateRoster(any(), any()); - } - - @Test - void stakingPeriodSetsCandidateRosterForEnabledFlag() { - // Simulate staking information - given(blockStore.getLastBlockInfo()) - .willReturn(BlockInfo.newBuilder() - .consTimeOfLastHandledTxn(new Timestamp(CONSENSUS_TIME_1234567.getEpochSecond(), 0)) - .build()); - given(context.consensusTime()).willReturn(CONSENSUS_TIME_1234567.plus(Duration.ofDays(2))); - - // Enable keyCandidateRoster - given(context.configuration()).willReturn(newConfig(DEFAULT_STAKING_PERIOD_MINS, true)); - - // Simulate an updated address book - final var nodeStore = simulateNodes(RosterCase.NODE_1, RosterCase.NODE_2, RosterCase.NODE_3); - given(dispatch.handleContext()).willReturn(handleContext); - given(handleContext.storeFactory()).willReturn(storeFactory); - given(storeFactory.readableStore(ReadableNodeStore.class)).willReturn(nodeStore); - given(stack.getWritableStates(notNull())).willReturn(writableStates); - simulateCandidateAndActiveRosters(); - - subject.process(dispatch, stack, context, StreamMode.RECORDS, false, Instant.EPOCH); - verify(tssBaseService).setCandidateRoster(notNull(), notNull()); - } - - private ReadableNodeStore simulateNodes(Node... nodes) { - final Map translated = Arrays.stream(nodes) - .collect(Collectors.toMap( - n -> EntityNumber.newBuilder().number(n.nodeId()).build(), node -> node)); - final WritableKVState nodeWritableKVState = new MapWritableKVState<>(NODES_KEY, translated); - given(writableStates.get(NODES_KEY)).willReturn(nodeWritableKVState); - final ReadableNodeStore nodeStore = new ReadableNodeStoreImpl(writableStates); - given(context.readableStore(ReadableNodeStore.class)).willReturn(nodeStore); - - return nodeStore; - } - - private void simulateCandidateAndActiveRosters() { - given(rosterState.get()) - .willReturn(new RosterState( - RosterCase.CANDIDATE_ROSTER_HASH.value(), - List.of(RoundRosterPair.newBuilder() - .roundNumber(12345) - .activeRosterHash(RosterCase.ACTIVE_ROSTER_HASH.value()) - .build()))); - given(writableStates.getSingleton(V0540RosterSchema.ROSTER_STATES_KEY)) - .willReturn(rosterState); - given(writableStates.get(V0540RosterSchema.ROSTER_KEY)) - .willReturn(new MapWritableKVState<>( - V0540RosterSchema.ROSTER_KEY, - Map.of( - RosterCase.CANDIDATE_ROSTER_HASH, - RosterCase.CURRENT_CANDIDATE_ROSTER, - RosterCase.ACTIVE_ROSTER_HASH, - RosterCase.ACTIVE_ROSTER))); - } - private Configuration newPeriodMinsConfig() { return newPeriodMinsConfig(DEFAULT_STAKING_PERIOD_MINS); } @@ -459,85 +292,4 @@ private Configuration newConfig(final long periodMins, final boolean keyCandidat .withValue("tss.keyCandidateRoster", keyCandidateRoster) .getOrCreateConfig(); } - - public static class RosterCase { - static final Bytes BYTES_1_2_3 = Bytes.wrap("1, 2, 3"); - static final Node NODE_1 = Node.newBuilder() - .nodeId(1) - .weight(10) - .gossipCaCertificate(BYTES_1_2_3) - .gossipEndpoint(ServiceEndpoint.newBuilder() - .ipAddressV4(Bytes.wrap("1, 1")) - .port(11) - .build()) - .build(); - public static final RosterEntry ROSTER_NODE_1 = RosterEntry.newBuilder() - .nodeId(NODE_1.nodeId()) - .weight(NODE_1.weight()) - .gossipCaCertificate(NODE_1.gossipCaCertificate()) - .gossipEndpoint(NODE_1.gossipEndpoint()) - .build(); - static final Node NODE_2 = Node.newBuilder() - .nodeId(2) - .weight(20) - .gossipCaCertificate(BYTES_1_2_3) - .gossipEndpoint(ServiceEndpoint.newBuilder() - .ipAddressV4(Bytes.wrap("2, 2")) - .port(22) - .build()) - .build(); - public static final RosterEntry ROSTER_NODE_2 = RosterEntry.newBuilder() - .nodeId(NODE_2.nodeId()) - .weight(NODE_2.weight()) - .gossipCaCertificate(NODE_2.gossipCaCertificate()) - .gossipEndpoint((ServiceEndpoint.newBuilder() - .ipAddressV4(Bytes.wrap("2, 2")) - .port(22) - .build())) - .build(); - static final Node NODE_3 = Node.newBuilder() - .nodeId(3) - .weight(30) - .gossipCaCertificate(BYTES_1_2_3) - .gossipEndpoint(ServiceEndpoint.newBuilder() - .ipAddressV4(Bytes.wrap("3, 3")) - .port(33) - .build()) - .build(); - public static final RosterEntry ROSTER_NODE_3 = RosterEntry.newBuilder() - .nodeId(NODE_3.nodeId()) - .weight(NODE_3.weight()) - .gossipCaCertificate(NODE_3.gossipCaCertificate()) - .gossipEndpoint(NODE_3.gossipEndpoint()) - .build(); - static final Node NODE_4 = Node.newBuilder() - .nodeId(4) - .weight(40) - .gossipCaCertificate(BYTES_1_2_3) - .gossipEndpoint(ServiceEndpoint.newBuilder() - .ipAddressV4(Bytes.wrap("4, 4")) - .port(44) - .build()) - .build(); - static final RosterEntry ROSTER_NODE_4 = RosterEntry.newBuilder() - .nodeId(NODE_4.nodeId()) - .weight(NODE_4.weight()) - .gossipCaCertificate(NODE_4.gossipCaCertificate()) - .gossipEndpoint(NODE_4.gossipEndpoint()) - .build(); - - public static final Roster CURRENT_CANDIDATE_ROSTER = Roster.newBuilder() - .rosterEntries(List.of(ROSTER_NODE_1, ROSTER_NODE_2)) - .build(); - public static final Roster ACTIVE_ROSTER = Roster.newBuilder() - .rosterEntries(ROSTER_NODE_1, ROSTER_NODE_2, ROSTER_NODE_3, ROSTER_NODE_4) - .build(); - - static final ProtoBytes CANDIDATE_ROSTER_HASH = ProtoBytes.newBuilder() - .value(RosterUtils.hash(CURRENT_CANDIDATE_ROSTER).getBytes()) - .build(); - static final ProtoBytes ACTIVE_ROSTER_HASH = ProtoBytes.newBuilder() - .value(RosterUtils.hash(ACTIVE_ROSTER).getBytes()) - .build(); - } } 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 0b74f3a95b49..804e36cebdea 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.node.app.workflows.handle.steps; import static com.hedera.hapi.node.freeze.FreezeType.FREEZE_ABORT; @@ -173,7 +188,7 @@ void testFreezeUpgradeWhenKeying() { .freeze(FreezeTransactionBody.newBuilder().freezeType(FREEZE_UPGRADE)); // when - subject.handleTxBody(state, txBody.build(), configWith(true, true)); + subject.handleTxBody(state, txBody.build(), configWith(true, false)); // then final var platformState = platformStateBackingStore.get(); @@ -190,7 +205,7 @@ void testFreezeUpgradeWhenKeyingButNotUsingRosterLifecycle() { .freeze(FreezeTransactionBody.newBuilder().freezeType(FREEZE_UPGRADE)); // when - subject.handleTxBody(state, txBody.build(), configWith(true, false)); + subject.handleTxBody(state, txBody.build(), configWith(false, false)); // then final var platformState = platformStateBackingStore.get(); @@ -214,7 +229,7 @@ void putsCandidateRosterWhenNotKeyingButUsingRosterLifecycle() { .build()); // when - subject.handleTxBody(state, txBody.build(), configWith(false, true)); + subject.handleTxBody(state, txBody.build(), configWith(true, true)); // then final var captor = ArgumentCaptor.forClass(Path.class); @@ -237,7 +252,7 @@ void worksAroundFailureToPutCandidateRoster() { .gossipEndpoint(new ServiceEndpoint(Bytes.EMPTY, 50211, "test.org")) .build()); - subject.handleTxBody(state, txBody.build(), configWith(false, true)); + subject.handleTxBody(state, txBody.build(), configWith(true, true)); verify(rosterExportHelper, never()).accept(any(), any()); } @@ -258,7 +273,7 @@ void exportsCandidateRosterIfRequestedEvenWhenNotUsingRosterLifecycle() { .build()); // when - subject.handleTxBody(state, txBody.build(), configWith(false, false)); + subject.handleTxBody(state, txBody.build(), configWith(false, true)); // then final var captor = ArgumentCaptor.forClass(Path.class); @@ -267,9 +282,9 @@ void exportsCandidateRosterIfRequestedEvenWhenNotUsingRosterLifecycle() { assertEquals("candidate-network.json", path.getFileName().toString()); } - private Configuration configWith(final boolean keyCandidateRoster, final boolean useRosterLifecycle) { + private Configuration configWith(final boolean useRosterLifecycle, final boolean createCandidateRoster) { return HederaTestConfigBuilder.create() - .withValue("tss.keyCandidateRoster", "" + keyCandidateRoster) + .withValue("addressBook.createCandidateRosterOnPrepareUpgrade", "" + createCandidateRoster) .withValue("addressBook.useRosterLifecycle", "" + useRosterLifecycle) .withValue("networkAdmin.exportCandidateRoster", "true") .withValue("networkAdmin.candidateRosterExportFile", "candidate-network.json") 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 f579261810d8..9ff6d15f6b56 100644 --- a/hedera-node/hedera-app/src/test/resources/bootstrap/network.json +++ b/hedera-node/hedera-app/src/test/resources/bootstrap/network.json @@ -28,8 +28,7 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - }, - "tssEncryptionKey": "ASM=" + } }, { "rosterEntry": { "nodeId": "1", @@ -61,8 +60,7 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - }, - "tssEncryptionKey": "ASM=" + } }, { "rosterEntry": { "nodeId": "2", @@ -94,8 +92,7 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - }, - "tssEncryptionKey": "ASM=" + } }, { "rosterEntry": { "nodeId": "3", @@ -127,37 +124,6 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - }, - "tssEncryptionKey": "ASM=" - }], - "tssMessages": [{ - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "9", - "tssMessage": "VkFMSUQ5" - }, { - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "8", - "tssMessage": "VkFMSUQ4" - }, { - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "3", - "tssMessage": "VkFMSUQz" - }, { - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "2", - "tssMessage": "VkFMSUQy" - }, { - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "7", - "tssMessage": "VkFMSUQ3" - }, { - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "5", - "tssMessage": "VkFMSUQ1" - }, { - "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", - "shareIndex": "10", - "tssMessage": "VkFMSUQxMA==" - }], - "ledgerId": "Lw==" + } + }] } \ No newline at end of file diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java index 29420222a867..7dab1706417e 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -86,10 +86,6 @@ import static com.hedera.hapi.node.base.HederaFunctionality.TRANSACTION_GET_FAST_RECORD; import static com.hedera.hapi.node.base.HederaFunctionality.TRANSACTION_GET_RECEIPT; import static com.hedera.hapi.node.base.HederaFunctionality.TRANSACTION_GET_RECORD; -import static com.hedera.hapi.node.base.HederaFunctionality.TSS_ENCRYPTION_KEY; -import static com.hedera.hapi.node.base.HederaFunctionality.TSS_MESSAGE; -import static com.hedera.hapi.node.base.HederaFunctionality.TSS_SHARE_SIGNATURE; -import static com.hedera.hapi.node.base.HederaFunctionality.TSS_VOTE; import static com.hedera.hapi.node.base.HederaFunctionality.UTIL_PRNG; import com.hedera.hapi.node.base.HederaFunctionality; @@ -347,10 +343,6 @@ public record ApiPermissionConfig( permissionKeys.put(NODE_CREATE, c -> c.createNode); permissionKeys.put(NODE_UPDATE, c -> c.updateNode); permissionKeys.put(NODE_DELETE, c -> c.deleteNode); - permissionKeys.put(TSS_MESSAGE, c -> c.tssMessage); - permissionKeys.put(TSS_VOTE, c -> c.tssVote); - permissionKeys.put(TSS_SHARE_SIGNATURE, c -> c.tssShareSignature); - permissionKeys.put(TSS_ENCRYPTION_KEY, c -> c.tssEncryptionKey); permissionKeys.put(STATE_SIGNATURE_TRANSACTION, c -> c.stateSignature); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java index 57fc2e8e268f..11110bb374e2 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/NetworkAdminConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -45,8 +45,7 @@ public record NetworkAdminConfig( @ConfigProperty(defaultValue = "application-override.properties") String upgradePropertyOverridesFile, @ConfigProperty(defaultValue = "api-permission-override.properties") String upgradePermissionOverridesFile, @ConfigProperty(defaultValue = "node-admin-keys.json") String upgradeNodeAdminKeysFile, - @ConfigProperty(defaultValue = "TssMessage,TssVote,TssShareSignature") @NetworkProperty - HederaFunctionalitySet nodeTransactionsAllowList, + @ConfigProperty(defaultValue = "NONE") @NetworkProperty HederaFunctionalitySet nodeTransactionsAllowList, @ConfigProperty(defaultValue = "network.json") @NodeProperty String diskNetworkExportFile, @ConfigProperty(defaultValue = "NEVER") DiskNetworkExport diskNetworkExport, @ConfigProperty(defaultValue = "false") @NodeProperty boolean exportCandidateRoster, 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 63ca23f43e83..7510132f445b 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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,34 +16,10 @@ package com.hedera.node.config.data; -import com.hedera.node.config.NetworkProperty; import com.swirlds.config.api.ConfigData; -import com.swirlds.config.api.ConfigProperty; -import java.time.Duration; /** - * Configuration for the TSS service. - * @param maxSharesPerNode The maximum number of shares that can be assigned to a node. - * @param timesToTrySubmission The number of times to retry a submission on getting an {@link IllegalStateException} - * @param retryDelay The delay between retries - * @param distinctTxnIdsToTry The number of distinct transaction IDs to try in the event of a duplicate id - * @param keyCandidateRoster A feature flag for TSS; set this to true to enable the process that will key - * @param keyActiveRoster A test-only configuration; set this to true to enable the process that will - * key the candidate roster with TSS key material, without waiting for upgrade - * boundary. - * @param signatureLivenessPeriodMinutes The amount of time a share signature is held in memory before being - * discarded in minutes - * @param ledgerSignatureFailureThreshold The number of consecutive failures to produce a ledger signature before - * logging an error + * Configuration for the TSS subsystem. */ @ConfigData("tss") -public record TssConfig( - @ConfigProperty(defaultValue = "3") @NetworkProperty int maxSharesPerNode, - @ConfigProperty(defaultValue = "50") @NetworkProperty int timesToTrySubmission, - @ConfigProperty(defaultValue = "5s") @NetworkProperty Duration retryDelay, - @ConfigProperty(defaultValue = "10") @NetworkProperty int distinctTxnIdsToTry, - @ConfigProperty(defaultValue = "false") @NetworkProperty boolean keyCandidateRoster, - @ConfigProperty(defaultValue = "false") @NetworkProperty boolean signWithLedgerId, - @ConfigProperty(defaultValue = "false") @NetworkProperty boolean keyActiveRoster, - @ConfigProperty(defaultValue = "5") @NetworkProperty int signatureLivenessPeriodMinutes, - @ConfigProperty(defaultValue = "2") @NetworkProperty int ledgerSignatureFailureThreshold) {} +public record TssConfig() {} 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 47fa7e9cf465..ba842e27f5ad 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,22 +21,15 @@ 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.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; @@ -47,27 +40,21 @@ 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; @@ -82,33 +69,10 @@ */ 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 -> { @@ -118,7 +82,7 @@ public void beforeEach(@NonNull final ExtensionContext extensionContext) { final var a = method.getAnnotation(GenesisHapiTest.class); final var bootstrapOverrides = Arrays.stream(a.bootstrapOverrides()) .collect(toMap(ConfigOverride::key, ConfigOverride::value)); - targetNetwork.startWith(bootstrapOverrides, nodeId -> Bytes.EMPTY, nodes -> Optional.empty()); + targetNetwork.startWith(bootstrapOverrides); HapiSpec.TARGET_NETWORK.set(targetNetwork); } else if (isAnnotated(method, RestartHapiTest.class)) { final var targetNetwork = @@ -127,22 +91,14 @@ public void beforeEach(@NonNull final ExtensionContext extensionContext) { 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); + case GENESIS -> targetNetwork.startWith(restartOverrides); + case SAME_VERSION -> targetNetwork.startWith(setupOverrides); + case UPGRADE_BOUNDARY -> startFromPreviousVersion(targetNetwork, setupOverrides); } switch (a.restartType()) { case GENESIS -> { @@ -241,12 +197,9 @@ private void bindThreadTargets( * * @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) { + @NonNull final EmbeddedNetwork targetNetwork, @NonNull final Map overrides) { final Map netOverrides = new HashMap<>(overrides); final var currentVersion = workingDirVersion(); final var previousVersion = currentVersion @@ -257,7 +210,7 @@ private void startFromPreviousVersion( .build("") .build(); netOverrides.put("hedera.services.version", HapiUtils.toString(previousVersion)); - targetNetwork.startWith(netOverrides, tssSourceFns.tssEncryptionKeyFn(), tssSourceFns.tssKeyMaterialFn()); + targetNetwork.startWith(netOverrides); } private FakeState postGenesisStateOf( 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 85a5c00a0372..70e710cf0e5b 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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 static com.hedera.node.app.info.DiskStartupNetworks.ARCHIVE; @@ -11,20 +26,15 @@ 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.Comparator; -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. @@ -44,14 +54,9 @@ protected AbstractLocalNode(@NonNull final NodeMetadata metadata) { } @Override - public @NonNull T initWorkingDir( - @NonNull final String configTxt, - @NonNull final LongFunction tssEncryptionKeyFn, - @NonNull final Function, Optional> tssKeyMaterialFn) { + public @NonNull T initWorkingDir(@NonNull final String configTxt) { requireNonNull(configTxt); - requireNonNull(tssEncryptionKeyFn); - requireNonNull(tssKeyMaterialFn); - recreateWorkingDir(requireNonNull(metadata.workingDir()), configTxt, tssEncryptionKeyFn, tssKeyMaterialFn); + recreateWorkingDir(requireNonNull(metadata.workingDir()), configTxt); workingDirInitialized = true; return self(); } 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 f7d92b22fae1..9986853989d0 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -18,8 +18,6 @@ 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; @@ -34,9 +32,6 @@ 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. @@ -164,13 +159,8 @@ default HederaNode getRequiredNode(@NonNull final NodeSelector selector) { * 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 startWith( - @NonNull final Map bootstrapOverrides, - @NonNull final LongFunction tssEncryptionKeyFn, - @NonNull final Function, Optional> tssKeyMaterialFn) { + default void startWith(@NonNull final Map bootstrapOverrides) { 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 0c2d613c26d1..8ae2c32e0bf9 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -17,21 +17,16 @@ 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 { /** @@ -81,16 +76,6 @@ public interface HederaNode { */ Path getExternalPath(@NonNull ExternalPath path); - /** - * 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 - */ - 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. * @@ -98,10 +83,7 @@ default HederaNode initWorkingDir(@NonNull final String configTxt) { * @return this */ @NonNull - HederaNode initWorkingDir( - @NonNull String configTxt, - @NonNull LongFunction tssEncryptionKeyFn, - @NonNull Function, Optional> tssKeyMaterialFn); + HederaNode initWorkingDir(@NonNull String configTxt); /** * Starts the node software. 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 c1d7c8921e82..8afc3c6aa40b 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -35,7 +35,6 @@ 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; @@ -46,7 +45,7 @@ import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.embedded.fakes.AbstractFakePlatform; -import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssBaseService; +import com.hedera.services.bdd.junit.hedera.embedded.fakes.LapsingBlockHashSigner; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; @@ -121,11 +120,11 @@ public abstract class AbstractEmbeddedHedera implements EmbeddedHedera { */ 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}). + * Non-final because the compiler can't tell that the {@link com.hedera.node.app.Hedera.BlockHashSignerFactory} + * lambda we give the {@link Hedera} constructor will always set this (the fake's delegate will ultimately need + * needs to be constructed from the Hedera instance's {@code HintsService} and {@code HistoryService}). */ - protected FakeTssBaseService tssBaseService; + protected LapsingBlockHashSigner blockHashSigner; protected AbstractEmbeddedHedera(@NonNull final EmbeddedNode node) { requireNonNull(node); @@ -149,11 +148,8 @@ protected AbstractEmbeddedHedera(@NonNull final EmbeddedNode node) { FakeServicesRegistry.FACTORY, new FakeServiceMigrator(), this::now, - appContext -> { - this.tssBaseService = new FakeTssBaseService(appContext); - return this.tssBaseService; - }, - DiskStartupNetworks::new); + DiskStartupNetworks::new, + () -> this.blockHashSigner = new LapsingBlockHashSigner()); version = (ServicesSoftwareVersion) hedera.getSoftwareVersion(); blockStreamEnabled = hedera.isBlockStreamEnabled(); Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdownNow)); @@ -222,11 +218,6 @@ public FakeState state() { return state; } - @Override - public FakeTssBaseService tssBaseService() { - return tssBaseService; - } - @Override public SoftwareVersion version() { return version; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedHedera.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedHedera.java index f81f12dc3a49..2ceef5b46fe5 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -19,7 +19,6 @@ 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; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; @@ -56,12 +55,6 @@ public interface EmbeddedHedera { */ FakeState state(); - /** - * Returns the fake TSS base service of the embedded Hedera node. - * @return the fake TSS base service of the embedded Hedera node - */ - FakeTssBaseService tssBaseService(); - /** * Returns the software version of the embedded Hedera node. * @return the software version of the embedded Hedera node diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNetwork.java index 0ccaae0d02e4..e9adf69e6910 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,14 +28,11 @@ 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; @@ -48,12 +45,8 @@ 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; @@ -113,23 +106,18 @@ public EmbeddedNetwork( */ 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()); + startVia(hedera -> hedera.restart(state), bootstrapOverrides); } @Override public void start() { - startWith(emptyMap(), nodeId -> Bytes.EMPTY, nodes -> Optional.empty()); + startWith(emptyMap()); } @Override - public void startWith( - @NonNull final Map bootstrapOverrides, - @NonNull final LongFunction tssEncryptionKeyFn, - @NonNull final Function, Optional> tssKeyMaterialFn) { + public void startWith(@NonNull final Map bootstrapOverrides) { requireNonNull(bootstrapOverrides); - requireNonNull(tssEncryptionKeyFn); - requireNonNull(tssKeyMaterialFn); - startVia(EmbeddedHedera::start, bootstrapOverrides, tssEncryptionKeyFn, tssKeyMaterialFn); + startVia(EmbeddedHedera::start, bootstrapOverrides); } @Override @@ -206,12 +194,9 @@ protected HapiPropertySource networkOverrides() { } private void startVia( - @NonNull final Consumer start, - @NonNull final Map bootstrapOverrides, - @NonNull final LongFunction tssEncryptionKeyFn, - @NonNull final Function, Optional> tssKeyMaterialFn) { + @NonNull final Consumer start, @NonNull final Map bootstrapOverrides) { // Initialize the working directory - embeddedNode.initWorkingDir(configTxt, tssEncryptionKeyFn, tssKeyMaterialFn); + embeddedNode.initWorkingDir(configTxt); if (!bootstrapOverrides.isEmpty()) { updateBootstrapProperties(embeddedNode.getExternalPath(APPLICATION_PROPERTIES), bootstrapOverrides); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNode.java index 7ace1e5a0451..a272d87b6a39 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -90,7 +90,7 @@ public HederaNode start() { } @Override - public EmbeddedNode initWorkingDir(@NonNull final String configTxt) { + public @NonNull EmbeddedNode initWorkingDir(@NonNull final String configTxt) { super.initWorkingDir(configTxt); updateUpgradeArtifactsProperty(getExternalPath(APPLICATION_PROPERTIES), getExternalPath(UPGRADE_ARTIFACTS_DIR)); return this; 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 db237fc3b353..3e1ef35aa4bd 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,12 +16,10 @@ package com.hedera.services.bdd.junit.hedera.embedded; -import static com.hedera.hapi.node.base.HederaFunctionality.TSS_SHARE_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static com.swirlds.platform.system.transaction.TransactionWrapperUtils.createAppPayloadWrapper; import static java.util.Objects.requireNonNull; -import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -29,6 +27,7 @@ import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeConsensusEvent; import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeEvent; import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeRound; +import com.hedera.services.bdd.junit.hedera.embedded.fakes.LapsingBlockHashSigner; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionResponse; @@ -40,7 +39,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.time.Instant; +import java.util.ArrayDeque; import java.util.List; +import java.util.Queue; import java.util.concurrent.ScheduledExecutorService; /** @@ -56,6 +57,7 @@ public class RepeatableEmbeddedHedera extends AbstractEmbeddedHedera implements private final FakeTime time = new FakeTime(FIXED_POINT, Duration.ZERO); private final SynchronousFakePlatform platform; + private final Queue pendingNodeSubmissions = new ArrayDeque<>(); // The amount of consensus time that will be simulated to elapse before the next transaction---note // that in repeatable mode, every transaction gets its own event, and each event gets its own round @@ -102,13 +104,13 @@ public TransactionResponse submit( new FakeEvent(nodeId, time.now(), semanticVersion, createAppPayloadWrapper(payload)); } if (response.getNodeTransactionPrecheckCode() == OK) { - handleNextRound(false); - // If handling this transaction scheduled TSS work, do it synchronously as well - while (tssBaseService.hasTssSubmission()) { + handleNextRound(); + // If handling this transaction scheduled node transactions, handle them now + while (!pendingNodeSubmissions.isEmpty()) { platform.lastCreatedEvent = null; - tssBaseService.executeNextTssSubmission(); + pendingNodeSubmissions.poll().run(); if (platform.lastCreatedEvent != null) { - handleNextRound(true); + handleNextRound(); } } } @@ -122,6 +124,14 @@ protected long validStartOffsetSecs() { return 0L; } + /** + * Returns the block hash signer for this embedded node that can be told to lapse into an unresponsive state + * and start ignoring signature requests. + */ + public LapsingBlockHashSigner blockHashSigner() { + return blockHashSigner; + } + /** * Returns the last consensus round number. */ @@ -139,16 +149,10 @@ public void setRoundDuration(@NonNull final Duration roundDuration) { } /** - * Executes the transaction in the last-created event within its own round, unless that transaction - * is a {@link HederaFunctionality#TSS_SHARE_SIGNATURE} transaction and we are instructed to skip - * signatures. - * @param skipsSignatureTxn whether to skip handling the last-created event if it is a signature txn + * Executes the transaction in the last-created event within its own round. */ - private void handleNextRound(final boolean skipsSignatureTxn) { + private void handleNextRound() { hedera.onPreHandle(platform.lastCreatedEvent, state); - if (skipsSignatureTxn && platform.lastCreatedEvent.function() == TSS_SHARE_SIGNATURE) { - return; - } final var round = platform.nextConsensusRound(); // Handle each transaction in own round hedera.handleWorkflow().handleRound(state, round); 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 deleted file mode 100644 index 6edda8d6752c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java +++ /dev/null @@ -1,282 +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.services.bdd.junit.hedera.embedded.fakes; - -import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; -import static java.util.Objects.requireNonNull; - -import com.hedera.cryptography.tss.api.TssMessage; -import com.hedera.cryptography.tss.api.TssParticipantDirectory; -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; -import com.hedera.node.app.tss.handlers.TssHandlers; -import com.hedera.node.app.tss.stores.ReadableTssStoreImpl; -import com.hedera.node.app.version.ServicesSoftwareVersion; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hedera.services.bdd.junit.HapiTest; -import com.swirlds.common.metrics.noop.NoOpMetrics; -import com.swirlds.common.utility.CommonUtils; -import com.swirlds.config.api.Configuration; -import com.swirlds.platform.system.InitTrigger; -import com.swirlds.state.State; -import com.swirlds.state.lifecycle.SchemaRegistry; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; -import java.util.ArrayDeque; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ForkJoinPool; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A fake implementation of the {@link TssBaseService} that, - *

    - *
  • Lets the author of an embedded {@link HapiTest} control whether the TSS base service ignores - * signature requests; and, when requests are not ignored,
  • - *
  • "Signs" messages by scheduling callback to its consumers using the SHA-384 hash of the - * message as the signature.
  • - *
- * Important: this class is not thread-safe, and should only be used as a mock in repeatable mode - * when specs are run one at a time. - */ -public class FakeTssBaseService implements TssBaseService { - private static final Logger log = LogManager.getLogger(FakeTssBaseService.class); - - private final FakeTssLibrary tssLibrary = new FakeTssLibrary(); - private final TssBaseServiceImpl delegate; - private final Queue pendingTssSubmission = new ArrayDeque<>(); - /** - * Copy-on-write list to avoid concurrent modification exceptions if a consumer unregisters - * itself in its callback. - */ - private final List> consumers = new CopyOnWriteArrayList<>(); - - /** - * The type of signing to perform. - */ - public enum Signing { - /** - * If not ignoring requests, provide a fake signature by hashing the message. - */ - FAKE, - /** - * Delegate to the real TSS base service. - */ - DELEGATE - } - - private Signing signing = Signing.DELEGATE; - private boolean ignoreRequests = false; - - public FakeTssBaseService(@NonNull final AppContext appContext) { - delegate = new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - pendingTssSubmission::offer, - tssLibrary, - pendingTssSubmission::offer, - new NoOpMetrics()); - } - - /** - * Returns the fake TSS library. - * @return the fake TSS library - */ - public FakeTssLibrary fakeTssLibrary() { - return tssLibrary; - } - - /** - * Returns whether there is a pending TSS submission. - * - * @return if the fake TSS base service has a pending TSS submission - */ - public boolean hasTssSubmission() { - return !pendingTssSubmission.isEmpty(); - } - - /** - * Executes the pending TSS submission and clears it. - */ - public void executeNextTssSubmission() { - requireNonNull(pendingTssSubmission.poll()).run(); - } - - /** - * When called, will start ignoring any requests for ledger signatures. - */ - public void startIgnoringRequests() { - ignoreRequests = true; - } - - /** - * When called, will stop ignoring any requests for ledger signatures. - */ - public void stopIgnoringRequests() { - ignoreRequests = false; - } - - /** - * Switches to using fake signatures. - */ - public void useFakeSignatures() { - signing = Signing.FAKE; - } - - /** - * Switches to using real signatures. - */ - public void useRealSignatures() { - signing = Signing.DELEGATE; - } - - @Override - public Status getStatus( - @NonNull final Roster roster, - @NonNull final Bytes ledgerId, - @NonNull final ReadableTssStoreImpl tssBaseStore) { - requireNonNull(roster); - requireNonNull(ledgerId); - requireNonNull(tssBaseStore); - return delegate.getStatus(roster, ledgerId, tssBaseStore); - } - - @Override - public void bootstrapLedgerId( - @NonNull final Roster roster, - @NonNull final HandleContext context, - @NonNull final Consumer ledgerIdConsumer) { - requireNonNull(roster); - requireNonNull(context); - requireNonNull(ledgerIdConsumer); - delegate.bootstrapLedgerId(roster, context, ledgerIdConsumer); - } - - @Override - public void requestLedgerSignature( - @NonNull final byte[] messageHash, @NonNull final Instant lastUsedConsensusTime) { - requireNonNull(messageHash); - requireNonNull(lastUsedConsensusTime); - switch (signing) { - case FAKE -> { - if (ignoreRequests) { - return; - } - final var mockSignature = noThrowSha384HashOf(messageHash); - // Simulate asynchronous completion of the ledger signature - CompletableFuture.runAsync(() -> consumers.forEach(consumer -> { - try { - consumer.accept(messageHash, mockSignature); - } catch (Exception e) { - log.error( - "Failed to provide signature {} on message {} to consumer {}", - CommonUtils.hex(mockSignature), - CommonUtils.hex(messageHash), - consumer, - e); - } - })); - } - case DELEGATE -> delegate.requestLedgerSignature(messageHash, lastUsedConsensusTime); - } - } - - @Override - public void setCandidateRoster(@NonNull final Roster roster, @NonNull final HandleContext context) { - requireNonNull(roster); - requireNonNull(context); - delegate.setCandidateRoster(roster, context); - } - - @Override - public void registerSchemas(@NonNull final SchemaRegistry registry) { - delegate.registerSchemas(registry); - } - - @Override - public void registerLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { - requireNonNull(consumer); - consumers.add(consumer); - delegate.registerLedgerSignatureConsumer(consumer); - } - - @Override - public void unregisterLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { - requireNonNull(consumer); - consumers.remove(consumer); - delegate.unregisterLedgerSignatureConsumer(consumer); - } - - @Override - public TssHandlers tssHandlers() { - return delegate.tssHandlers(); - } - - @Override - @NonNull - public Roster chooseRosterForNetwork( - @NonNull State state, - @NonNull InitTrigger trigger, - @NonNull ServiceMigrator serviceMigrator, - @NonNull ServicesSoftwareVersion version, - @NonNull final Configuration configuration, - @NonNull final Roster overrideRoster) { - return delegate.chooseRosterForNetwork(state, trigger, serviceMigrator, version, configuration, overrideRoster); - } - - @Override - public void regenerateKeyMaterial(@NonNull final State state) { - delegate.regenerateKeyMaterial(state); - } - - @Override - public void ensureParticipantDirectoryKnown(@NonNull final State state) { - delegate.ensureParticipantDirectoryKnown(state); - } - - @Override - public Bytes ledgerIdFrom( - @NonNull final TssParticipantDirectory directory, @NonNull final List tssMessages) { - requireNonNull(directory); - requireNonNull(tssMessages); - return delegate.ledgerIdFrom(directory, tssMessages); - } - - @Override - public TssMessage getTssMessageFromBytes(Bytes wrap, TssParticipantDirectory directory) { - return delegate.getTssMessageFromBytes(wrap, directory); - } - - @Override - 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/junit/hedera/embedded/fakes/FakeTssLibrary.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssLibrary.java deleted file mode 100644 index 9fbdb5c50e83..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssLibrary.java +++ /dev/null @@ -1,193 +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.services.bdd.junit.hedera.embedded.fakes; - -import static java.util.Collections.emptyList; -import static java.util.Objects.requireNonNull; - -import com.hedera.cryptography.bls.BlsPrivateKey; -import com.hedera.cryptography.bls.BlsPublicKey; -import com.hedera.cryptography.bls.BlsSignature; -import com.hedera.cryptography.bls.SignatureSchema; -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; -import com.hedera.cryptography.tss.api.TssShareSignature; -import com.hedera.node.app.tss.api.FakeFieldElement; -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 edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.math.BigInteger; -import java.util.List; - -public class FakeTssLibrary implements TssLibrary { - private static final String VALID_MESSAGE_PREFIX = "VALID"; - private static final String INVALID_MESSAGE_PREFIX = "INVALID"; - - private static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(new byte[] {1}); - public static final BlsPrivateKey PRIVATE_KEY = - new BlsPrivateKey(new FakeFieldElement(BigInteger.valueOf(42L)), SIGNATURE_SCHEMA); - public static final BlsPublicKey FAKE_LEDGER_ID = - new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(42L)), SIGNATURE_SCHEMA); - public static final BlsSignature FAKE_SIGNATURE = - new BlsSignature(new FakeGroupElement(BigInteger.valueOf(1L)), SIGNATURE_SCHEMA); - - public interface DirectoryAssertion { - void assertExpected(@NonNull TssParticipantDirectory directory) throws AssertionError; - } - - @Nullable - private DirectoryAssertion decryptDirectoryAssertion; - - @Nullable - private DirectoryAssertion rekeyGenerationDirectoryAssertion; - - @Nullable - private List decryptedShares; - - /** - * Returns a valid message with the given index. - * @param i the index - * @return the message - */ - public static TssMessage validMessage(final int i) { - return new FakeTssMessage((VALID_MESSAGE_PREFIX + i).getBytes()); - } - - /** - * Returns an invalid message with the given index. - * @param i the index - * @return the message - */ - public static TssMessage invalidMessage(final int i) { - return new FakeTssMessage((INVALID_MESSAGE_PREFIX + i).getBytes()); - } - - /** - * Returns the index of the share in the message. - * @param message the message - * @return the index - */ - public static int getShareIndex(final TssMessage message) { - final var s = new String(message.toBytes()); - return Integer.parseInt(s.substring(s.lastIndexOf('D') + 1)); - } - - @NonNull - @Override - public TssMessage generateTssMessage(@NonNull final TssParticipantDirectory tssParticipantDirectory) { - return new FakeTssMessage(new byte[0]); - } - - /** - * Sets up the behavior to exhibit when receiving a call to - * {@link #generateTssMessage(TssParticipantDirectory, TssPrivateShare)}. - */ - public void setupRekeyGeneration(@NonNull final DirectoryAssertion rekeyGenerationDirectoryAssertion) { - this.rekeyGenerationDirectoryAssertion = requireNonNull(rekeyGenerationDirectoryAssertion); - } - - @Override - public @NonNull TssMessage generateTssMessage( - @NonNull final TssParticipantDirectory directory, @NonNull final TssPrivateShare privateShare) { - requireNonNull(directory); - requireNonNull(privateShare); - if (rekeyGenerationDirectoryAssertion != null) { - rekeyGenerationDirectoryAssertion.assertExpected(directory); - } - // The fake always returns a valid message - return validMessage(privateShare.shareId()); - } - - @Override - public boolean verifyTssMessage( - @NonNull final TssParticipantDirectory participantDirectory, @NonNull final Bytes tssMessage) { - return new String(tssMessage.toByteArray()).startsWith(VALID_MESSAGE_PREFIX); - } - - /** - * Sets up the behavior to exhibit when receiving a call to {@link #decryptPrivateShares(TssParticipantDirectory, List)}. - * @param decryptDirectoryAssertion the assertion to make about the directory - * @param decryptedShareIds the ids of the private shares to return - */ - public void setupDecryption( - @NonNull final DirectoryAssertion decryptDirectoryAssertion, - @NonNull final List decryptedShareIds) { - requireNonNull(decryptedShareIds); - this.decryptDirectoryAssertion = requireNonNull(decryptDirectoryAssertion); - this.decryptedShares = decryptedShareIds.stream() - .map(id -> new TssPrivateShare(id, PRIVATE_KEY)) - .toList(); - } - - @Override - public @NonNull List decryptPrivateShares( - @NonNull final TssParticipantDirectory directory, @NonNull final List tssMessages) { - requireNonNull(directory); - requireNonNull(tssMessages); - if (decryptDirectoryAssertion != null) { - decryptDirectoryAssertion.assertExpected(directory); - } - return decryptedShares == null ? emptyList() : decryptedShares; - } - - @NonNull - @Override - public List computePublicShares( - @NonNull final TssParticipantDirectory participantDirectory, - @NonNull final List validTssMessages) { - System.out.println("Computing public shares from " + validTssMessages.size() + " messages"); - return List.of(); - } - - @NonNull - @Override - public BlsPublicKey aggregatePublicShares(@NonNull final List publicShares) { - return FAKE_LEDGER_ID; - } - - @NonNull - @Override - public TssShareSignature sign(@NonNull final TssPrivateShare privateShare, @NonNull final byte[] message) { - return new TssShareSignature( - privateShare.shareId(), - new BlsSignature(new FakeGroupElement(BigInteger.valueOf(privateShare.shareId())), SIGNATURE_SCHEMA)); - } - - @Override - public boolean verifySignature( - @NonNull final TssParticipantDirectory participantDirectory, - @NonNull final List publicShares, - @NonNull final TssShareSignature signature) { - return true; - } - - @NonNull - @Override - public BlsSignature aggregateSignatures(@NonNull final List partialSignatures) { - return FAKE_SIGNATURE; - } - - @NonNull - @Override - public TssMessage getTssMessageFromBytes(Bytes tssMessage, TssParticipantDirectory participantDirectory) { - return new FakeTssMessage(new byte[0]); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/LapsingBlockHashSigner.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/LapsingBlockHashSigner.java new file mode 100644 index 000000000000..1c0cff2c2dda --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/LapsingBlockHashSigner.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.services.bdd.junit.hedera.embedded.fakes; + +import com.hedera.node.app.blocks.BlockHashSigner; +import com.hedera.node.app.tss.TssBlockHashSigner; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.concurrent.CompletableFuture; + +/** + * A {@link BlockHashSigner} that can start ignoring requests for ledger signatures when requested. + */ +public class LapsingBlockHashSigner implements BlockHashSigner { + private final BlockHashSigner delegate; + + private boolean ignoreRequests = false; + + public LapsingBlockHashSigner() { + this.delegate = new TssBlockHashSigner(); + } + + /** + * When called, will start ignoring any requests for ledger signatures. + */ + public void startIgnoringRequests() { + ignoreRequests = true; + } + + /** + * When called, will stop ignoring any requests for ledger signatures. + */ + public void stopIgnoringRequests() { + ignoreRequests = false; + } + + @Override + public boolean isReady() { + return delegate.isReady(); + } + + @Override + public CompletableFuture signFuture(@NonNull final Bytes blockHash) { + return ignoreRequests ? new CompletableFuture<>() : delegate.signFuture(blockHash); + } +} 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 15f3c8510380..875fc57ef773 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,25 +16,18 @@ 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) { @@ -51,15 +44,6 @@ 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 cf92363ab77b..7e187d33a5c9 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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.subprocess; import static com.hedera.node.app.info.DiskStartupNetworks.GENESIS_NETWORK_JSON; @@ -24,7 +39,6 @@ import com.hedera.node.internal.network.Network; import com.hedera.node.internal.network.NodeMetadata; import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.pbj.runtime.io.stream.ReadableStreamingData; import com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension; import com.hedera.services.bdd.junit.hedera.AbstractGrpcNetwork; @@ -50,7 +64,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.SplittableRandom; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -413,8 +426,7 @@ private void refreshOverrideNetworks(@NonNull final ReassignPorts reassignPorts) } catch (IOException e) { throw new UncheckedIOException(e); } - final var overrideNetwork = WorkingDirUtils.networkFrom( - configTxt, i -> Bytes.EMPTY, rosterEntries -> Optional.empty(), OnlyRoster.YES); + final var overrideNetwork = WorkingDirUtils.networkFrom(configTxt, OnlyRoster.YES); final var genesisNetworkPath = node.getExternalPath(DATA_CONFIG_DIR).resolve(GENESIS_NETWORK_JSON); final var isGenesis = genesisNetworkPath.toFile().exists(); // Only write override-network.json if a node is not starting from genesis; otherwise it will adopt @@ -463,8 +475,7 @@ private NodeMetadata withReassignedPorts( metadata.nodeOrThrow() .copyBuilder() .gossipEndpoint(endpoints.getLast(), endpoints.getFirst()) - .build(), - metadata.tssEncryptionKey()); + .build()); } private void reinitializePorts() { 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 1d6c3138d4d7..36e1c1eebfa7 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 @@ -1,40 +1,44 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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.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 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.node.state.roster.RosterEntry; -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.platform.crypto.CryptoStatic; -import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.math.BigInteger; import java.nio.file.Path; import java.security.cert.CertificateEncodingException; import java.text.ParseException; 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; /** @@ -44,32 +48,6 @@ public class AddressBookUtils { 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); - }; 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 d3483480ebb4..1fedee4fafff 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 @@ -1,4 +1,19 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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.utils; import static com.hedera.node.app.hapi.utils.CommonPbjConverters.toPbj; @@ -16,7 +31,6 @@ 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; @@ -38,11 +52,8 @@ 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 { @@ -112,18 +123,10 @@ 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, - @NonNull final LongFunction tssEncryptionKeyFn, - @NonNull final Function, Optional> tssKeyMaterialFn) { + public static void recreateWorkingDir(@NonNull final Path workingDir, @NonNull final String configTxt) { requireNonNull(workingDir); requireNonNull(configTxt); - requireNonNull(tssEncryptionKeyFn); - requireNonNull(tssKeyMaterialFn); // Clean up any existing directory structure rm(workingDir); @@ -135,7 +138,7 @@ public static void recreateWorkingDir( workingDir.resolve(DATA_DIR).resolve(UPGRADE_DIR).resolve(CURRENT_DIR)); // 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, OnlyRoster.NO); + final var network = networkFrom(configTxt, OnlyRoster.NO); writeStringUnchecked( workingDir.resolve(DATA_DIR).resolve(CONFIG_FOLDER).resolve(GENESIS_NETWORK_JSON), Network.JSON.toJSON(network)); @@ -397,19 +400,11 @@ public enum OnlyRoster { * set to the same certificates used in each position by a test network. * * @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 * @param onlyRoster if true, only the roster entries will be set in the network * @return the network */ - public static Network networkFrom( - @NonNull final String configTxt, - @NonNull final LongFunction tssEncryptionKeyFn, - @NonNull final Function, Optional> tssKeyMaterialFn, - @NonNull final OnlyRoster onlyRoster) { + public static Network networkFrom(@NonNull final String configTxt, @NonNull final OnlyRoster onlyRoster) { requireNonNull(configTxt); - requireNonNull(tssEncryptionKeyFn); - requireNonNull(tssKeyMaterialFn); requireNonNull(onlyRoster); final var certs = AddressBookUtils.certsFor(configTxt); final var nodeMetadata = Arrays.stream(configTxt.split("\n")) @@ -425,29 +420,22 @@ public static Network networkFrom( .rosterEntry(new RosterEntry(nodeId, weight, cert, gossipEndpoints)); if (onlyRoster == OnlyRoster.NO) { metadata.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, - CLASSIC_ADMIN_KEY)) - .tssEncryptionKey(tssEncryptionKeyFn.apply(nodeId)); + 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, + CLASSIC_ADMIN_KEY)); } return metadata.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(); + return Network.newBuilder().nodeMetadata(nodeMetadata).build(); } private static ServiceEndpoint endpointFrom(@NonNull final String hostLiteral, @NonNull final String portLiteral) { 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 index 3ae167fc95ae..08bb09861dc3 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,13 +28,4 @@ public enum StartupAssets { * 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/spec/HapiSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java index d6d7b09df00f..f6bb840535ed 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2020-2025 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. @@ -20,7 +20,6 @@ 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; -import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; import static com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension.REPEATABLE_KEY_GENERATOR; import static com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension.SHARED_NETWORK; import static com.hedera.services.bdd.junit.hedera.ExternalPath.RECORD_STREAMS_DIR; @@ -63,14 +62,11 @@ import com.hedera.hapi.node.state.schedule.ScheduledCounts; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.state.token.Token; -import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.node.app.fixtures.state.FakeState; import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.service.schedule.ScheduleService; import com.hedera.node.app.service.schedule.impl.schemas.V0570ScheduleSchema; import com.hedera.node.app.service.token.TokenService; -import com.hedera.node.app.tss.TssBaseService; import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension; import com.hedera.services.bdd.junit.hedera.HederaNetwork; @@ -535,18 +531,6 @@ public void sleepConsensusTime(@NonNull final Duration duration) { return state.getWritableStates(ScheduleService.NAME).get(V0570ScheduleSchema.SCHEDULED_COUNTS_KEY); } - /** - * Get the {@link WritableKVState} for the embedded network's tssMessages, if this spec is targeting an - * embedded network. - * - * @return the embedded tss messages state - * @throws IllegalStateException if this spec is not targeting an embedded network - */ - public @NonNull WritableKVState embeddedTssMsgStateOrThrow() { - final var state = embeddedStateOrThrow(); - return state.getWritableStates(TssBaseService.NAME).get(TSS_MESSAGE_MAP_KEY); - } - /** * Get the {@link WritableKVState} for the embedded network's rosters, if this spec is targeting an * embedded network. diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/EmbeddedVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/EmbeddedVerbs.java index b390b7b23f97..ad77ba82e7e6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/EmbeddedVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/EmbeddedVerbs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -35,11 +35,7 @@ import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.hapi.node.state.token.Token; -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; import com.hedera.node.app.hapi.utils.CommonPbjConverters; import com.hedera.node.app.throttle.ThrottleAccumulator; import com.hedera.node.app.workflows.TransactionInfo; @@ -52,8 +48,6 @@ import com.hedera.services.bdd.spec.utilops.embedded.MutateScheduleCountsOp; import com.hedera.services.bdd.spec.utilops.embedded.MutateStakingInfosOp; import com.hedera.services.bdd.spec.utilops.embedded.MutateTokenOp; -import com.hedera.services.bdd.spec.utilops.embedded.MutateTssMessagesOp; -import com.hedera.services.bdd.spec.utilops.embedded.MutateTssVotesOp; import com.hedera.services.bdd.spec.utilops.embedded.ViewAccountOp; import com.hedera.services.bdd.spec.utilops.embedded.ViewKVStateOp; import com.hedera.services.bdd.spec.utilops.embedded.ViewMappingValueOp; @@ -124,27 +118,6 @@ public static MutateStakingInfosOp mutateStakingInfos( return new MutateStakingInfosOp(mutation); } - /** - * Returns an operation that allows the test author to directly mutate the TSS messages. - * - * @param mutation the mutation to apply to the TSS messages - * @return the operation that will mutate the TSS messages - */ - public static MutateTssMessagesOp mutateTssMessages( - @NonNull final Consumer> mutation) { - return new MutateTssMessagesOp(mutation); - } - - /** - * Returns an operation that allows the test author to directly mutate the TSS votes. - * - * @param mutation the mutation to apply to the TSS votes - * @return the operation that will mutate the TSS votes - */ - public static MutateTssVotesOp mutateTssVotes( - @NonNull final Consumer> mutation) { - return new MutateTssVotesOp(mutation); - } /** * Returns an operation that allows the test author to directly mutate an account. * diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java index 45255420fbeb..e67c5832b72c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -19,17 +19,10 @@ import static com.hedera.node.app.hapi.utils.CommonPbjConverters.fromPbj; import static com.hedera.services.bdd.junit.hedera.NodeSelector.byNodeId; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doingContextual; -import static java.util.Objects.requireNonNull; -import com.google.protobuf.ByteString; -import com.hedera.cryptography.tss.api.TssMessage; -import com.hedera.hapi.services.auxiliary.tss.legacy.TssMessageTransactionBody; import com.hedera.node.app.tss.TssBaseService; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssLibrary; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.SpecOperation; -import com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Duration; import com.hederahashgraph.api.proto.java.SignatureMap; @@ -41,8 +34,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import java.util.function.Consumer; -import java.util.function.LongFunction; -import java.util.function.LongUnaryOperator; /** * Factory for spec operations that support exercising TSS, especially in embedded mode. @@ -59,7 +50,7 @@ private TssVerbs() { */ public static SpecOperation startIgnoringTssSignatureRequests() { return doingContextual( - spec -> spec.repeatableEmbeddedHederaOrThrow().tssBaseService().startIgnoringRequests()); + spec -> spec.repeatableEmbeddedHederaOrThrow().blockHashSigner().startIgnoringRequests()); } /** @@ -69,66 +60,16 @@ public static SpecOperation startIgnoringTssSignatureRequests() { */ public static SpecOperation stopIgnoringTssSignatureRequests() { return doingContextual( - spec -> spec.repeatableEmbeddedHederaOrThrow().tssBaseService().stopIgnoringRequests()); + spec -> spec.repeatableEmbeddedHederaOrThrow().blockHashSigner().stopIgnoringRequests()); } /** - * Returns an operation that simulates a re-keying scenario in the context of a repeatable embedded test. - * @param dabEdits the edits to make before creating the candidate roster - * @param nodeStakes the node stakes to have in place at the stake period boundary - * @param tssMessageSims the TSS message simulations to apply - * @return the operation that will simulate the re-keying scenario - */ - public static RekeyScenarioOp rekeyingScenario( - @NonNull final RekeyScenarioOp.DabEdits dabEdits, - @NonNull final LongUnaryOperator nodeStakes, - @NonNull final LongFunction tssMessageSims) { - return new RekeyScenarioOp( - dabEdits, nodeStakes, tssMessageSims, RekeyScenarioOp.BlockSigningType.SIGN_WITH_FAKE); - } - - /** - * Returns an operation that simulates a re-keying scenario in the context of a repeatable embedded test. - * @param dabEdits the edits to make before creating the candidate roster - * @param nodeStakes the node stakes to have in place at the stake period boundary - * @param tssMessageSims the TSS message simulations to apply - * @param blockSigningType the type of block signing to perform - * @return the operation that will simulate the re-keying scenario - */ - public static RekeyScenarioOp rekeyingScenario( - @NonNull final RekeyScenarioOp.DabEdits dabEdits, - @NonNull final LongUnaryOperator nodeStakes, - @NonNull final LongFunction tssMessageSims, - @NonNull final RekeyScenarioOp.BlockSigningType blockSigningType) { - return new RekeyScenarioOp(dabEdits, nodeStakes, tssMessageSims, blockSigningType); - } - - /** - * Returns an operation that simulates the synchronous submission and execution of a given TSS message - * from the given node id in the context of a repeatable embedded test. - * + * Submits a node transaction customized by the given body spec if the {@link HapiSpec}'s target network is an + * embedded network in repeatable mode. * @param nodeId the node id - * @param sourceRosterHash the source roster hash - * @param targetRosterHash the target roster hash - * @param tssMessage the TSS message - * @return the operation that will submit the TSS message + * @param spec the spec + * @param bodySpec the body spec */ - public static SpecOperation submitTssMessage( - final long nodeId, - @NonNull final Bytes sourceRosterHash, - @NonNull final Bytes targetRosterHash, - @NonNull final TssMessage tssMessage) { - requireNonNull(tssMessage); - return doingContextual(spec -> submitRepeatable( - nodeId, - spec, - b -> b.setTssMessage(TssMessageTransactionBody.newBuilder() - .setSourceRosterHash(fromPbj(sourceRosterHash)) - .setTargetRosterHash(fromPbj(targetRosterHash)) - .setShareIndex(FakeTssLibrary.getShareIndex(tssMessage)) - .setTssMessage(ByteString.copyFrom(tssMessage.toBytes()))))); - } - private static void submitRepeatable( final long nodeId, @NonNull final HapiSpec spec, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/embedded/MutateTssMessagesOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/embedded/MutateTssMessagesOp.java deleted file mode 100644 index 5e1ce8f33b2a..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/embedded/MutateTssMessagesOp.java +++ /dev/null @@ -1,50 +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.services.bdd.spec.utilops.embedded; - -import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import com.swirlds.state.spi.WritableKVState; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Consumer; - -/** - * Allows the test author to mutate the TSS messages. - */ -public class MutateTssMessagesOp extends UtilOp { - private final Consumer> mutation; - - public MutateTssMessagesOp( - @NonNull final Consumer> mutation) { - this.mutation = requireNonNull(mutation); - } - - @Override - protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { - requireNonNull(spec); - final var state = spec.embeddedStateOrThrow(); - mutation.accept(state.getWritableStates(TssBaseService.NAME).get(TSS_MESSAGE_MAP_KEY)); - spec.commitEmbeddedState(); - return false; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/embedded/MutateTssVotesOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/embedded/MutateTssVotesOp.java deleted file mode 100644 index 1b6569696923..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/embedded/MutateTssVotesOp.java +++ /dev/null @@ -1,46 +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.services.bdd.spec.utilops.embedded; - -import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; -import com.hedera.node.app.tss.TssBaseService; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import com.swirlds.state.spi.WritableKVState; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Consumer; - -public class MutateTssVotesOp extends UtilOp { - private final Consumer> mutation; - - public MutateTssVotesOp(@NonNull final Consumer> mutation) { - this.mutation = requireNonNull(mutation); - } - - @Override - protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { - requireNonNull(spec); - final var state = spec.embeddedStateOrThrow(); - mutation.accept(state.getWritableStates(TssBaseService.NAME).get(TSS_VOTE_MAP_KEY)); - spec.commitEmbeddedState(); - return false; - } -} 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 deleted file mode 100644 index c81e2633a9f2..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java +++ /dev/null @@ -1,556 +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.services.bdd.spec.utilops.tss; - -import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUpdater.scaleStakeToWeight; -import static com.hedera.node.app.tss.handlers.TssUtils.computeNodeShares; -import static com.hedera.node.app.tss.handlers.TssUtils.computeSharesFromWeights; -import static com.hedera.node.app.tss.handlers.TssUtils.getThresholdForTssMessages; -import static com.hedera.services.bdd.junit.hedera.NodeSelector.exceptNodeIds; -import static com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssLibrary.FAKE_LEDGER_ID; -import static com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssLibrary.FAKE_SIGNATURE; -import static com.hedera.services.bdd.junit.hedera.utils.AddressBookUtils.CLASSIC_FIRST_NODE_ACCOUNT_NUM; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.nodeCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.nodeDelete; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.EmbeddedVerbs.mutateStakingInfos; -import static com.hedera.services.bdd.spec.utilops.EmbeddedVerbs.mutateTssMessages; -import static com.hedera.services.bdd.spec.utilops.EmbeddedVerbs.mutateTssVotes; -import static com.hedera.services.bdd.spec.utilops.TssVerbs.submitTssMessage; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doWithStartupConfig; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doingContextual; -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.sourcingContextual; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitUntilStartOfNextStakingPeriod; -import static com.hedera.services.bdd.suites.hip869.NodeCreateTest.generateX509Certificates; -import static com.hedera.services.bdd.suites.utils.validation.ValidationScenarios.TINYBARS_PER_HBAR; -import static com.swirlds.platform.roster.RosterRetriever.getActiveRosterHash; -import static com.swirlds.platform.roster.RosterRetriever.getCandidateRosterHash; -import static com.swirlds.platform.roster.RosterRetriever.retrieveActive; -import static java.lang.Long.parseLong; -import static java.util.Objects.requireNonNull; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.hedera.cryptography.tss.api.TssMessage; -import com.hedera.hapi.block.stream.Block; -import com.hedera.hapi.node.base.AccountID; -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; -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.roster.RosterService; -import com.hedera.pbj.runtime.ParseException; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hedera.services.bdd.junit.hedera.HederaNode; -import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssLibrary; -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.SpecOperation; -import com.hedera.services.bdd.spec.utilops.UtilOp; -import com.hedera.services.bdd.spec.utilops.streams.assertions.BlockStreamAssertion; -import com.swirlds.platform.state.service.WritableRosterStore; -import com.swirlds.state.spi.CommittableWritableStates; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.security.cert.CertificateEncodingException; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.LongFunction; -import java.util.function.LongUnaryOperator; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; - -/** - * A convenience operation to test TSS roster rekeying scenarios in repeatable embedded mode. A scenario is built - * by specifying, - *
    - *
  1. The distribution of HBAR stakes at the stake period boundary, and hence the number of shares each - * node should have in the candidate roster.
  2. - *
  3. The DAB edits that should be performed before the staking period change.
  4. - *
  5. The simulated message-submission behavior of the non-embedded nodes after the staking period - * change; where each non-embedded node will generate up to one {@link TssMessage} per share it is implied - * to own by the active roster in the embedded state, with that message being valid or invalid according - * to the {@link FakeTssLibrary} as requested.
  6. - *
- */ -public class RekeyScenarioOp extends UtilOp implements BlockStreamAssertion { - private static final Logger log = LogManager.getLogger(RekeyScenarioOp.class); - private static final int NA = -1; - private static final byte[] GOSSIP_CERTIFICATE; - - private record NonEmbeddedTssMessage(long nodeId, @NonNull TssMessage tssMessage) {} - - /** - * The number of shares each node holds in the active TSS directory. - */ - private final Map activeShares = new TreeMap<>(); - /** - * The number of shares each node is expected to hold in the candidate TSS directory. - */ - private final Map expectedShares = new HashMap<>(); - /** - * The TSS messages submitted by the non-embedded nodes, in consensus order. - */ - private final List nonEmbeddedTssMessages = new ArrayList<>(); - - @Nullable - private Roster currentRoster; - - @Nullable - private Bytes currentRosterHash; - - @Nullable - private Bytes targetRosterHash; - - private int actualVotes = 0; - private int actualMessages = 0; - private int expectedVotes = NA; - private int expectedMessages = NA; - private int actualTssSignatures = 0; - - static { - try { - GOSSIP_CERTIFICATE = generateX509Certificates(1).getFirst().getEncoded(); - } catch (CertificateEncodingException e) { - throw new IllegalStateException(e); - } - } - - /** - * Enumerates the possible behaviors of non-embedded nodes after a staking period change. - */ - public enum TssMessageSim { - /** - * The non-embedded node skips submitting TSS messages for its private shares. - */ - SKIP_MESSAGES, - /** - * The non-embedded node submits valid TSS messages for its private shares. - */ - VALID_MESSAGES, - /** - * The non-embedded node submits invalid TSS messages for its private shares. - */ - INVALID_MESSAGES - } - - /** - * A record that encapsulates the DAB edits to be performed before the staking period change. - * - * @param nodesToDelete the set of node IDs to delete - * @param numNodesToCreate the number of nodes to create - */ - public record DabEdits(@NonNull Set nodesToDelete, int numNodesToCreate) { - public static DabEdits NO_DAB_EDITS = new DabEdits(Set.of(), 0); - - public DabEdits { - requireNonNull(nodesToDelete); - } - } - - /** - * A stake distribution where all nodes have equal stakes. - */ - public static final LongUnaryOperator UNEQUAL_NODE_STAKES = - nodeId -> (nodeId + 1) * 1_000_000_000L * TINYBARS_PER_HBAR; - - public static final LongUnaryOperator NODE_ZERO_DOMINANT_STAKES = nodeId -> switch ((int) nodeId) { - case 0 -> 10_000_000_000L * TINYBARS_PER_HBAR; - case 1, 2 -> 1_000_000_000L * TINYBARS_PER_HBAR; - default -> 0; - }; - - /** - * Factory for TSS message simulations that returns the given behaviors for each non-embedded node. - */ - public static final Function, LongFunction> TSS_MESSAGE_SIMS = - sims -> nodeId -> sims.get((int) ((nodeId - 1) % sims.size())); - - private final DabEdits dabEdits; - private final LongUnaryOperator nodeStakes; - private final LongFunction tssMessageSims; - private final BlockSigningType blockSigningType; - - /** - * Constructs a {@link RekeyScenarioOp} with the given stake distribution, DAB edits, and TSS message submission - * behaviors. - * - * @param dabEdits the DAB edits - * @param nodeStakes the stake distribution - * @param tssMessageSims the TSS message submission behaviors - * @param blockSigningType the block signing type - */ - public RekeyScenarioOp( - @NonNull final DabEdits dabEdits, - @NonNull final LongUnaryOperator nodeStakes, - @NonNull final LongFunction tssMessageSims, - @NonNull final BlockSigningType blockSigningType) { - this.dabEdits = requireNonNull(dabEdits); - this.nodeStakes = requireNonNull(nodeStakes); - this.tssMessageSims = requireNonNull(tssMessageSims); - this.blockSigningType = requireNonNull(blockSigningType); - } - - @Override - protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { - requireNonNull(spec); - allRunFor( - spec, - Stream.of( - setupScenarioMocks(), - enableTss(), - submitDabOps(), - crossStakePeriodBoundary(), - simulateOtherNodeTssMessages()) - .flatMap(Function.identity()) - .toList()); - return false; - } - - @Override - public boolean test(@NonNull final Block block) throws AssertionError { - final var blockNo = block.items().getFirst().blockHeaderOrThrow().number(); - log.info( - "Pre-block#{}, votes={}/{}, messages={}/{}", - blockNo, - actualVotes, - (expectedVotes == NA) ? "?" : expectedVotes, - actualMessages, - (expectedMessages == NA) ? "?" : expectedMessages); - observeInteractionsIn(block); - log.info( - "Post-block#{}, votes={}/{}, messages={}/{}, tssSignatures={}", - blockNo, - actualVotes, - (expectedVotes == NA) ? "?" : expectedVotes, - actualMessages, - (expectedMessages == NA) ? "?" : expectedMessages, - actualTssSignatures); - boolean votesAndMessagesMatch = true; - if (expectedMessages != NA && expectedVotes != NA) { - if (actualMessages > expectedMessages) { - Assertions.fail( - "Too many messages submitted, expected " + expectedMessages + " but got " + actualMessages); - } - if (actualVotes > expectedVotes) { - Assertions.fail("Too many votes submitted, expected " + expectedVotes + " but got " + actualVotes); - } - votesAndMessagesMatch = (actualMessages == expectedMessages) && (actualVotes == expectedVotes); - } - boolean signaturesMatch = - switch (blockSigningType) { - case SIGN_WITH_FAKE -> actualTssSignatures == 0; - case SIGN_WITH_LEDGER_ID -> actualTssSignatures > 0; - }; - return votesAndMessagesMatch && signaturesMatch; - } - - private void observeInteractionsIn(@NonNull final Block block) { - for (final var item : block.items()) { - if (item.hasEventTransaction()) { - try { - final var wrapper = Transaction.PROTOBUF.parse( - item.eventTransactionOrThrow().applicationTransactionOrThrow()); - final var signedTxn = SignedTransaction.PROTOBUF.parse(wrapper.signedTransactionBytes()); - final var body = TransactionBody.PROTOBUF.parse(signedTxn.bodyBytes()); - if (body.nodeAccountIDOrElse(AccountID.DEFAULT).accountNumOrElse(0L) - == CLASSIC_FIRST_NODE_ACCOUNT_NUM) { - if (body.hasTssMessage()) { - actualMessages++; - } else if (body.hasTssVote()) { - actualVotes++; - } - } - } catch (ParseException e) { - Assertions.fail(e.getMessage()); - } - } - } - final var blockProof = block.items().getLast().blockProofOrThrow(); - if (blockProof.blockSignature().equals(Bytes.wrap(FAKE_SIGNATURE.toBytes()))) { - actualTssSignatures++; - } - } - - /** - * Returns a stream of operations that enable TSS feature flags. - */ - private Stream enableTss() { - if (blockSigningType == BlockSigningType.SIGN_WITH_LEDGER_ID) { - return Stream.of( - overriding("tss.keyCandidateRoster", "true"), overriding("tss.signWithLedgerId", "true") - // overridingTwo("tss.signWithLedgerId", "true", - // "tss.maxSharesPerNode", "4") - ); - } else { - return Stream.of(overriding("tss.keyCandidateRoster", "true")); - } - } - - /** - * Returns a stream of operations that perform the requested DAB edits before the stake period boundary is crossed. - */ - private Stream submitDabOps() { - final List ops = new ArrayList<>(); - for (int i = 0, n = dabEdits.numNodesToCreate(); i < n; i++) { - ops.add(newKeyNamed("adminKey" + i)); - ops.add(nodeCreate("newNode" + i).adminKey("adminKey" + i).gossipCaCertificate(GOSSIP_CERTIFICATE)); - } - dabEdits.nodesToDelete.forEach(nodeId -> ops.add(nodeDelete("" + nodeId))); - return ops.stream(); - } - - /** - * Returns a stream of operations that performs the embedded state mutations and mock expectations to - * enact the desired stake period boundary crossing scenario. In particular, this requires: - *
    - *
  1. Setting the node hbar stakes in state to the desired distribution (which determines the weights - * used in the participant directory derived from the candidate roster).
  2. - *
  3. Setting a well-known collection of TSS messages in state for the active roster hash.
  4. - *
  5. Preparing the {@link FakeTssLibrary} to certify these messages as valid.
  6. - *
  7. Preparing the {@link FakeTssLibrary} to decrypt the desired number of private shares - * for the embedded node, when it receives exactly the well-known {@link TssMessage}s.
  8. - *
  9. Preparing the {@link FakeTssLibrary} to return a well-known {@link TssMessage} for each - * mock private share, assuming the candidate participant directory has the expected - * node weights implied by the stake distribution at the start of the new period.
  10. - *
- */ - private Stream setupScenarioMocks() { - return Stream.of(extractRosterMetadata(), setActiveRosterKeyMaterial(), setRequestedStakes()); - } - - /** - * Returns a stream of operations that cross the stake period boundary, triggering the rekeying process. - */ - private Stream crossStakePeriodBoundary() { - return Stream.of( - doWithStartupConfig( - "staking.periodMins", - stakePeriodMins -> waitUntilStartOfNextStakingPeriod(parseLong(stakePeriodMins))), - // This transaction is now first in a new staking period and should trigger the TSS rekeying process, - // in particular a successful TssMessage from the embedded node (and potentially a TssVote if the - // non-embedded nodes submit sufficient TssMessages). - cryptoCreate("rekeyingTransaction")); - } - - /** - * Returns a stream of operations that simulate the desired TSS message submission behavior of the - * non-embedded nodes. - */ - private Stream simulateOtherNodeTssMessages() { - return Stream.of(doingContextual(spec -> { - final var nextShareId = new AtomicInteger(activeShares.get(0L)); - final var numValidMessages = new AtomicInteger(expectedMessages); - spec.targetNetworkOrThrow().nodesFor(exceptNodeIds(0L)).stream() - .map(HederaNode::getNodeId) - .forEach(nodeId -> { - final var sim = tssMessageSims.apply(nodeId); - switch (sim) { - case SKIP_MESSAGES -> nextShareId.getAndAdd(activeShares.get(nodeId)); - case VALID_MESSAGES, INVALID_MESSAGES -> { - for (int i = 0, n = activeShares.get(nodeId); i < n; i++) { - final var j = nextShareId.getAndIncrement(); - nonEmbeddedTssMessages.add(new NonEmbeddedTssMessage( - nodeId, - sim == TssMessageSim.VALID_MESSAGES - ? FakeTssLibrary.validMessage(j) - : FakeTssLibrary.invalidMessage(j))); - } - numValidMessages.getAndAdd( - (sim == TssMessageSim.VALID_MESSAGES) ? activeShares.get(nodeId) : 0); - } - } - }); - Collections.shuffle(nonEmbeddedTssMessages); - requireNonNull(currentRosterHash); - targetRosterHash = getCandidateRosterHash(spec.embeddedStateOrThrow()); - final var totalShares = - activeShares.values().stream().mapToInt(Integer::intValue).sum(); - final var thresholdShares = getThresholdForTssMessages(totalShares); - expectedVotes = numValidMessages.get() >= thresholdShares ? 1 : 0; - allRunFor( - spec, - nonEmbeddedTssMessages.stream() - .map(m -> submitTssMessage(m.nodeId(), currentRosterHash, targetRosterHash, m.tssMessage())) - .toArray(SpecOperation[]::new)); - })); - } - - /** - * Returns an operation that extracts roster metadata from the embedded state to be used in the scenario. - */ - private SpecOperation extractRosterMetadata() { - return doingContextual(spec -> { - final var state = spec.embeddedStateOrThrow(); - 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); - - 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()); - activeEntries.set( - 0, activeEntries.getFirst().copyBuilder().weight(10).build()); - rosterStore.putActiveRoster(new Roster(activeEntries), roundNo + 1); - ((CommittableWritableStates) writableStates).commit(); - - // Extract all the roster metadata for the active roster - currentRoster = requireNonNull(retrieveActive(state, roundNo)); - currentRosterHash = getActiveRosterHash(state, roundNo); - computeNodeShares( - currentRoster.rosterEntries(), - spec.startupProperties().getInteger("tss.maxSharesPerNode")) - .forEach((nodeId, numShares) -> activeShares.put(nodeId, numShares.intValue())); - expectedMessages = activeShares.get(0L); - // Prepare the FakeTssLibrary to decrypt the private shares of the embedded node - embeddedHedera - .tssBaseService() - .fakeTssLibrary() - .setupDecryption( - directory -> {}, - IntStream.range(0, activeShares.get(0L)).boxed().toList()); - }); - } - - /** - * Returns an operation that sets the requested node stakes in state for the given spec. These stakes - * should define the weights used in the participant directory derived from the candidate roster. - */ - private SpecOperation setRequestedStakes() { - return sourcingContextual(spec -> mutateStakingInfos(infos -> { - final Map stakes = new HashMap<>(); - spec.targetNetworkOrThrow().nodes().forEach(node -> { - final var key = new EntityNumber(node.getNodeId()); - final var info = requireNonNull(infos.get(key)); - final var stake = nodeStakes.applyAsLong(node.getNodeId()); - infos.put( - key, - info.copyBuilder().stake(stake).stakeToReward(stake).build()); - stakes.put(node.getNodeId(), stake); - log.info("Set stake for node {} to {} tinybars", node.getNodeId(), stake); - }); - final var totalWeight = spec.startupProperties().getInteger("staking.sumOfConsensusWeights"); - final var totalStake = - stakes.values().stream().mapToLong(Long::longValue).sum(); - final var weights = stakes.keySet().stream().collect(Collectors.toMap(Function.identity(), nodeId -> - (long) scaleStakeToWeight(stakes.get(nodeId), totalStake, totalWeight))); - final var maxShares = spec.startupProperties().getInteger("tss.maxSharesPerNode"); - computeSharesFromWeights(weights, maxShares) - .forEach((nodeId, numShares) -> expectedShares.put(nodeId, numShares.intValue())); - spec.repeatableEmbeddedHederaOrThrow() - .tssBaseService() - .fakeTssLibrary() - .setupRekeyGeneration(directory -> { - expectedShares.forEach((nodeId, expectedNumShares) -> assertEquals( - expectedNumShares, - directory.ownedShares(nodeId).size(), - "Wrong number of shares for node" + nodeId)); - }); - })); - } - - /** - * Returns an operation that sets the key material for the active roster in the embedded state. - * This has two components. - *
    - *
  1. First, the synthetic TSS messages whose keys specify the active roster hash.
  2. - *
  3. Second, the synthetic TSS votes whose keys specify the source roster hash (which - * is empty, since the current roster is in the first active roster in this scenario) and - * whose values specify the active roster hash; and concur on the consensus subset of TSS - * messages used to form the TSS keys for the active roster (which for convenience we take - * in order of share index).
  4. - *
- */ - private SpecOperation setActiveRosterKeyMaterial() { - final var nextShareId = new AtomicInteger(0); - final var voteSet = new BitSet(); - return blockingOrder( - mutateTssMessages(tssMessages -> { - requireNonNull(currentRosterHash); - final var totalShares = activeShares.values().stream() - .mapToInt(Integer::intValue) - .sum(); - final var thresholdShares = getThresholdForTssMessages(totalShares); - activeShares.forEach((nodeId, numShares) -> { - for (int i = 0; i < numShares; i++) { - final var shareId = nextShareId.getAndIncrement(); - final var key = new TssMessageMapKey(currentRosterHash, shareId); - final var tssMessage = Bytes.wrap(FakeTssLibrary.validMessage((int) key.sequenceNumber()) - .toBytes()); - final var value = TssMessageTransactionBody.newBuilder() - .sourceRosterHash(Bytes.EMPTY) - .targetRosterHash(currentRosterHash) - .shareIndex(key.sequenceNumber()) - .tssMessage(tssMessage) - .build(); - tssMessages.put(key, value); - if (voteSet.cardinality() < thresholdShares) { - voteSet.set(shareId); - } - } - }); - }), - mutateTssVotes(tssVotes -> { - final var tssVote = Bytes.wrap(voteSet.toByteArray()); - requireNonNull(currentRosterHash); - requireNonNull(currentRoster).rosterEntries().forEach(entry -> { - final var key = new TssVoteMapKey(currentRosterHash, entry.nodeId()); - final var vote = TssVoteTransactionBody.newBuilder() - .tssVote(tssVote) - .sourceRosterHash(Bytes.EMPTY) - .targetRosterHash(currentRosterHash) - .ledgerId(Bytes.wrap(FAKE_LEDGER_ID.toBytes())) - .build(); - tssVotes.put(key, vote); - }); - })); - } - - public enum BlockSigningType { - SIGN_WITH_LEDGER_ID, - SIGN_WITH_FAKE - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java index 0870b5465951..6af46800ae4e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermExecutionTest.java @@ -1,7 +1,23 @@ -// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2025 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.hip423; import static com.hedera.services.bdd.junit.ContextRequirement.FEE_SCHEDULE_OVERRIDES; +import static com.hedera.services.bdd.junit.RepeatableReason.NEEDS_VIRTUAL_TIME_FOR_FAST_EXECUTION; import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; @@ -80,6 +96,7 @@ 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.RepeatableHapiTest; import com.hedera.services.bdd.junit.support.TestLifecycle; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; @@ -750,8 +767,7 @@ public Stream executionWithCryptoInsufficientAccountBalanceFails() }))); } - @HapiTest - @Order(14) + @RepeatableHapiTest(NEEDS_VIRTUAL_TIME_FOR_FAST_EXECUTION) public Stream executionWithCryptoSenderDeletedFails() { long noBalance = 0L; long senderBalance = 100L; @@ -763,7 +779,7 @@ public Stream executionWithCryptoSenderDeletedFails() { cryptoCreate(RECEIVER).balance(noBalance), scheduleCreate(FAILED_XFER, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, transferAmount))) .waitForExpiry() - .withRelativeExpiry(SENDER_TXN, 4) + .withRelativeExpiry(SENDER_TXN, 5) .recordingScheduledTxn() .designatingPayer(PAYING_ACCOUNT) .via(CREATE_TX), @@ -778,15 +794,13 @@ public Stream executionWithCryptoSenderDeletedFails() { .hasWaitForExpiry() .isNotExecuted() .isNotDeleted() - .hasRelativeExpiry(SENDER_TXN, 4) + .hasRelativeExpiry(SENDER_TXN, 5) .hasRecordedScheduledTxn(), triggerSchedule(FAILED_XFER), getAccountBalance(RECEIVER).hasTinyBars(noBalance), withOpContext((spec, opLog) -> { var triggeredTx = getTxnRecord(CREATE_TX).scheduled(); - allRunFor(spec, triggeredTx); - Assertions.assertEquals( ACCOUNT_DELETED, triggeredTx.getResponseRecord().getReceipt().getStatus(), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableTssTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableTssTests.java index 1166f8990544..4a1531c40c6d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableTssTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableTssTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -18,38 +18,25 @@ import static com.hedera.node.config.types.StreamMode.RECORDS; import static com.hedera.services.bdd.junit.RepeatableReason.NEEDS_TSS_CONTROL; -import static com.hedera.services.bdd.junit.RepeatableReason.NEEDS_VIRTUAL_TIME_FOR_FAST_EXECUTION; 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.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.TssVerbs.rekeyingScenario; import static com.hedera.services.bdd.spec.utilops.TssVerbs.startIgnoringTssSignatureRequests; import static com.hedera.services.bdd.spec.utilops.TssVerbs.stopIgnoringTssSignatureRequests; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockStreamMustIncludePassFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doAdhoc; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.BlockSigningType.SIGN_WITH_FAKE; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.BlockSigningType.SIGN_WITH_LEDGER_ID; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.DabEdits.NO_DAB_EDITS; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.NODE_ZERO_DOMINANT_STAKES; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.TSS_MESSAGE_SIMS; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.TssMessageSim.INVALID_MESSAGES; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.TssMessageSim.VALID_MESSAGES; -import static com.hedera.services.bdd.spec.utilops.tss.RekeyScenarioOp.UNEQUAL_NODE_STAKES; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.hedera.hapi.block.stream.Block; import com.hedera.node.app.blocks.BlockStreamManager; -import com.hedera.services.bdd.junit.LeakyRepeatableHapiTest; import com.hedera.services.bdd.junit.RepeatableHapiTest; import com.hedera.services.bdd.junit.TargetEmbeddedMode; -import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssBaseService; import com.hedera.services.bdd.spec.utilops.streams.assertions.BlockStreamAssertion; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Order; @@ -65,12 +52,12 @@ public class RepeatableTssTests { * *

This test follows three main steps:

*
    - *
  • Instructs the {@link FakeTssBaseService} to start ignoring signature requests and + *
  • Instructs the fake TSS base service to start ignoring signature requests and * produces several blocks. In this scenario, each transaction is placed into its own round * since the service is operating in repeatable mode.
  • *
  • Verifies that no blocks are written, as no block proofs are available, which is the * expected behavior when the service is ignoring signature requests.
  • - *
  • Reactivates the {@link FakeTssBaseService}, creates another block, and verifies that + *
  • Reactivates the fake TSS base service, creates another block, and verifies that * the {@link BlockStreamManager} processes pending block proofs. It checks that the expected * blocks are written within a brief period after the service resumes normal behavior.
  • *
@@ -97,44 +84,6 @@ Stream blockStreamManagerCatchesUpWithIndirectProofs() { })); } - /** - * Creates a rekeying scenario where the embedded node receives the threshold number of valid TSS messages. - */ - @LeakyRepeatableHapiTest( - value = {NEEDS_TSS_CONTROL, NEEDS_VIRTUAL_TIME_FOR_FAST_EXECUTION}, - overrides = {"tss.keyCandidateRoster"}) - Stream embeddedNodeVotesGivenThresholdValidMessages() { - final var scenario = rekeyingScenario( - // Changing stakes is enough to ensure the candidate roster is different from the active roster - NO_DAB_EDITS, - // Give unequal stake to all nodes (so they have different numbers of shares in the candidate roster) - UNEQUAL_NODE_STAKES, - // Submit invalid messages from node1, to verify the embedded node votes waits for the required - // number of threshold valid messages - TSS_MESSAGE_SIMS.apply(List.of(INVALID_MESSAGES, VALID_MESSAGES, VALID_MESSAGES)), - SIGN_WITH_FAKE); - return hapiTest(blockStreamMustIncludePassFrom(spec -> scenario), scenario); - } - - /** - * Creates a rekeying scenario where the embedded node receives the threshold number of valid TSS messages. - */ - @LeakyRepeatableHapiTest( - value = {NEEDS_TSS_CONTROL, NEEDS_VIRTUAL_TIME_FOR_FAST_EXECUTION}, - overrides = {"tss.keyCandidateRoster", "tss.signWithLedgerId"}) - Stream blockSigningHappyPath() { - final var scenario = rekeyingScenario( - // Changing stakes is enough to ensure the candidate roster is different from the active roster - NO_DAB_EDITS, - // Give unequal stake to all nodes (so they have different numbers of shares in the candidate roster) - NODE_ZERO_DOMINANT_STAKES, - // Submit invalid messages from node1, to verify the embedded node votes waits for the required - // number of threshold valid messages - TSS_MESSAGE_SIMS.apply(List.of(INVALID_MESSAGES, VALID_MESSAGES, VALID_MESSAGES)), - SIGN_WITH_LEDGER_ID); - return hapiTest(blockStreamMustIncludePassFrom(spec -> scenario), scenario); - } - /** * A {@link BlockStreamAssertion} used to verify the presence of some number {@code n} of expected indirect proofs * in the block stream. When constructed, it assumes proof construction is paused, and fails if any block 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 3ccf526dd253..c5bac54c2b78 100644 --- a/hedera-node/test-clients/src/main/java/module-info.java +++ b/hedera-node/test-clients/src/main/java/module-info.java @@ -27,7 +27,6 @@ exports com.hedera.services.bdd.spec.utilops.lifecycle; exports com.hedera.services.bdd.spec.utilops.lifecycle.ops; exports com.hedera.services.bdd.spec.utilops.mod; - exports com.hedera.services.bdd.spec.utilops.tss; exports com.hedera.services.bdd.spec.utilops.pauses; exports com.hedera.services.bdd.spec.utilops.streams; exports com.hedera.services.bdd.spec.utilops.upgrade; @@ -64,8 +63,6 @@ exports com.hedera.services.bdd.utils; exports com.hedera.services.bdd.junit.restart; - 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; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/AddressBookConfig.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/AddressBookConfig.java index 61d5ed75d360..e2f8c68a8573 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/AddressBookConfig.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/AddressBookConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2020-2025 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. @@ -47,4 +47,5 @@ public record AddressBookConfig( @ConfigProperty(defaultValue = "false") boolean forceUseOfConfigAddressBook, @ConfigProperty(defaultValue = "data/saved/address_book") String addressBookDirectory, @ConfigProperty(defaultValue = "50") int maxRecordedAddressBookFiles, - @ConfigProperty(defaultValue = "false") boolean useRosterLifecycle) {} + @ConfigProperty(defaultValue = "false") boolean useRosterLifecycle, + @ConfigProperty(defaultValue = "true") boolean createCandidateRosterOnPrepareUpgrade) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoader.java index 53e9bf02dee3..2c236ed9b65a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -25,10 +25,6 @@ import static com.swirlds.platform.crypto.CryptoStatic.loadKeys; import static com.swirlds.platform.state.address.AddressBookNetworkUtils.isLocal; -import com.hedera.cryptography.asciiarmored.AsciiArmoredFiles; -import com.hedera.cryptography.bls.BlsKeyPair; -import com.hedera.cryptography.bls.BlsPrivateKey; -import com.hedera.cryptography.bls.BlsPublicKey; import com.swirlds.common.crypto.config.CryptoConfig; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; @@ -208,16 +204,6 @@ public class EnhancedKeyStoreLoader { */ private final Map agrPrivateKeys; - /** - * The private tss encryption keys loaded from disk. - */ - private final Map tssPrivateKeys; - - /** - * The public tss encryption keys. - */ - private final Map tssPublicKeys; - /** * The X.509 Certificates loaded from the key stores. */ @@ -258,8 +244,6 @@ private EnhancedKeyStoreLoader( this.agrPrivateKeys = HashMap.newHashMap(addressBook.getSize()); this.agrCertificates = HashMap.newHashMap(addressBook.getSize()); this.localNodes = HashSet.newHashSet(addressBook.getSize()); - this.tssPrivateKeys = HashMap.newHashMap(addressBook.getSize()); - this.tssPublicKeys = HashMap.newHashMap(addressBook.getSize()); } /** @@ -314,11 +298,6 @@ public EnhancedKeyStoreLoader scan() throws KeyLoadingException, KeyStoreExcepti localNodes.add(nodeId); sigPrivateKeys.compute( nodeId, (k, v) -> resolveNodePrivateKey(nodeId, nodeAlias, KeyCertPurpose.SIGNING)); - - BlsPrivateKey tssPrivateKey = resolveTssPrivateKey(nodeId, nodeAlias); - if (tssPrivateKey != null) { - tssPrivateKeys.put(nodeId, tssPrivateKey); - } } sigCertificates.compute( @@ -330,32 +309,6 @@ public EnhancedKeyStoreLoader scan() throws KeyLoadingException, KeyStoreExcepti return this; } - @Nullable - private BlsPrivateKey resolveTssPrivateKey(@NonNull final NodeId nodeId, @NonNull final String nodeAlias) - throws KeyLoadingException { - Objects.requireNonNull(nodeId, MSG_NODE_ID_NON_NULL); - Objects.requireNonNull(nodeAlias, MSG_NODE_ALIAS_NON_NULL); - - Path keyLocation = keyStoreDirectory.resolve(String.format("t-private-%s.tss", nodeAlias)); - if (Files.exists(keyLocation)) { - logger.trace( - STARTUP.getMarker(), - "Found tss encryption private key for node {} [ fileName = {} ]", - nodeId, - keyLocation.getFileName()); - try { - return AsciiArmoredFiles.readPrivateKey(keyLocation); - } catch (final Exception e) { - logger.warn( - STARTUP.getMarker(), - "Failed to read TSS encryption private key from disk, will generate a new key.", - e); - return null; - } - } - return null; - } - /** * Iterates over the local nodes and creates the agreement key and certificate for each. This method should be * called after {@link #scan()} and before {@link #verify()}. @@ -396,28 +349,6 @@ public EnhancedKeyStoreLoader generate() SecureRandom.getInstanceStrong()); agrCertificates.put(node, agrCert); } - - if (!tssPrivateKeys.containsKey(node) || tssPrivateKeys.get(node) == null) { - // Create a new public/private key pair for the TSS encryption key - BlsKeyPair tssKeyPair = CryptoStatic.generateBlsKeyPair(SecureRandom.getInstanceStrong()); - tssPrivateKeys.put(node, tssKeyPair.privateKey()); - tssPublicKeys.put(node, tssKeyPair.publicKey()); - // Write the private key to disk - final String nodeAlias = - nameToAlias(addressBook.getAddress(node).getSelfName()); - Path tssEncryptionPrivateKeyLocation = - keyStoreDirectory.resolve(String.format("t-private-%s.tss", nodeAlias)); - try { - AsciiArmoredFiles.writeKey(tssEncryptionPrivateKeyLocation, tssKeyPair.privateKey()); - } catch (IOException e) { - logger.error(ERROR.getMarker(), "Failed to write TSS encryption private key to disk", e); - throw new KeyGeneratingException("Failed to write TSS encryption private key to disk", e); - } - } else { - // Create the BlsPublicKey from the private TSS encryption key - BlsPrivateKey tssPrivateKey = tssPrivateKeys.get(node); - tssPublicKeys.put(node, tssPrivateKey.createPublicKey()); - } } return this; } @@ -475,16 +406,6 @@ public EnhancedKeyStoreLoader verify(@NonNull final AddressBook validatingBook) throw new KeyLoadingException("No certificate found for node %s [ alias = %s, purpose = %s ]" .formatted(nodeId, nodeAlias, KeyCertPurpose.AGREEMENT)); } - - if (!tssPrivateKeys.containsKey(nodeId)) { - throw new KeyLoadingException( - "No TSS private key found for node %s [ alias = %s ]".formatted(nodeId, nodeAlias)); - } - - if (!tssPublicKeys.containsKey(nodeId)) { - throw new KeyLoadingException( - "No TSS public key found for node %s [ alias = %s ]".formatted(nodeId, nodeAlias)); - } } if (!sigCertificates.containsKey(nodeId)) { @@ -559,12 +480,7 @@ public Map keysAndCerts(@NonNull final AddressBook validat final KeyPair sigKeyPair = new KeyPair(sigCert.getPublicKey(), sigPrivateKey); final KeyPair agrKeyPair = new KeyPair(agrCert.getPublicKey(), agrPrivateKey); - - final BlsPrivateKey tssPrivateKey = tssPrivateKeys.get(nodeId); - final BlsPublicKey tssPublicKey = tssPublicKeys.get(nodeId); - - final KeysAndCerts kc = new KeysAndCerts( - sigKeyPair, agrKeyPair, sigCert, agrCert, publicStores, tssPrivateKey, tssPublicKey); + final KeysAndCerts kc = new KeysAndCerts(sigKeyPair, agrKeyPair, sigCert, agrCert, publicStores); keysAndCerts.put(nodeId, kc); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/KeysAndCerts.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/KeysAndCerts.java index 3e6a7ef945b7..e7cb79ef453f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/KeysAndCerts.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/KeysAndCerts.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2024 Hedera Hashgraph, LLC + * Copyright (C) 2021-2025 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. @@ -17,8 +17,6 @@ package com.swirlds.platform.crypto; import com.hedera.cryptography.bls.BlsKeyPair; -import com.hedera.cryptography.bls.BlsPrivateKey; -import com.hedera.cryptography.bls.BlsPublicKey; import com.swirlds.common.crypto.internal.CryptoUtils; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.Key; @@ -62,9 +60,7 @@ public record KeysAndCerts( KeyPair agrKeyPair, X509Certificate sigCert, X509Certificate agrCert, - PublicStores publicStores, - BlsPrivateKey privateTssEncryptionKey, - BlsPublicKey publicTssEncryptionKey) { + PublicStores publicStores) { private static final int SIG_SEED = 2; private static final int AGR_SEED = 0; private static final int TSS_ENCRYPTION_KEY_SEED = 3; @@ -117,7 +113,7 @@ public static KeysAndCerts loadExistingAndCreateAgrKeyIfMissing( publicStores.setCertificate(KeyCertPurpose.AGREEMENT, agreementCert, dnA); } - return new KeysAndCerts(signingKeyPair, agreementKeyPair, signingCert, agreementCert, publicStores, null, null); + return new KeysAndCerts(signingKeyPair, agreementKeyPair, signingCert, agreementCert, publicStores); } private static KeyPair getKeyPair(final KeyStore privateKeyStore, final char[] password, final String storeName) @@ -203,14 +199,7 @@ public static KeysAndCerts generate( final BlsKeyPair blsKeyPair = CryptoStatic.generateBlsKeyPair(tssEncryptionKeyRandom); - return new KeysAndCerts( - sigKeyPair, - agrKeyPair, - sigCert, - agrCert, - publicStores, - blsKeyPair.privateKey(), - blsKeyPair.publicKey()); + return new KeysAndCerts(sigKeyPair, agrKeyPair, sigCert, agrCert, publicStores); } /** diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoaderTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoaderTest.java index 22295cbee743..94747fe79f12 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoaderTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/crypto/EnhancedKeyStoreLoaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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. @@ -81,18 +81,12 @@ void validateTestDataDirectory() { assertThat(testDataDirectory.resolve("enhanced-valid-no-agreement-key")) .exists() .isNotEmptyDirectory(); - assertThat(testDataDirectory.resolve("enhanced-valid-no-tss-key")) - .exists() - .isNotEmptyDirectory(); assertThat(testDataDirectory.resolve("enhanced-invalid-case-1")) .exists() .isNotEmptyDirectory(); assertThat(testDataDirectory.resolve("enhanced-invalid-case-2")) .exists() .isNotEmptyDirectory(); - assertThat(testDataDirectory.resolve("enhanced-valid-corrupt-tss-key")) - .exists() - .isNotEmptyDirectory(); assertThat(testDataDirectory.resolve("legacy-valid").resolve("public.pfx")) .exists() .isNotEmptyFile(); @@ -117,8 +111,6 @@ void validateTestDataDirectory() { "hybrid-valid", "enhanced-valid", "enhanced-valid-no-agreement-key", - "enhanced-valid-no-tss-key", - "enhanced-valid-corrupt-tss-key" }) void keyStoreLoaderPositiveTest(final String directoryName) throws IOException, KeyLoadingException, KeyStoreException { @@ -151,8 +143,6 @@ void keyStoreLoaderPositiveTest(final String directoryName) assertThat(keysAndCerts.sigCert()).isNotNull(); assertThat(keysAndCerts.agrKeyPair()).isNotNull(); assertThat(keysAndCerts.sigKeyPair()).isNotNull(); - assertThat(keysAndCerts.privateTssEncryptionKey()).isNotNull(); - assertThat(keysAndCerts.publicTssEncryptionKey()).isNotNull(); } assertThat(addr.getSigPublicKey()).isNotNull(); diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-alice.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-alice.pem deleted file mode 100644 index 92b7e50d3f8d..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-alice.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCwIWWNEdngJ0F1 -0o6WU5S/K1TN5f5gj6UrzF1cMJwwMbnM16Iopz4EvqejZMTwMyxiPflQU1ogy4Yi -REIQb5qStvib26RfZpbLzW+aKv7yn6bmUGKnyMaGw3q9DkpztiaKE/5jBv36x6oC -qHdi2FqHmYM2trbL/hwwQXYdkvmcYoaDThaTBwLnnvMeRFpABzGupPpa628dHCXG -iDm1NMpX12ZQiZW2cN5I1Ek+y3dMcySEkUMhPkz9F0NAwXDF1+TcbV79WuryxGrL -488RaWt/bpUqMOHWZquwVfrOls+3u1NbDoZPQnEU6XLOyGpLPZWWYaqQSPQH0xgG -YDxG4PkSme467Me2mAt8Uz0k71OI5NF/AsFl4yXtQGlmRI+wGbFeqsx8Ma4Eh8/i -/rCnvGtMjATE4W4ULGE2+hZtciw7JxC9ohZ2v/vk7EU2q0CujjqXmRJN7OJZ0m2U -SmciWlFTVunJJziMSF2nPwhBfRM2IMKQlLvnwWGfLUOI2BgW5mcCAwEAAQKCAYAD -Sp3k5HEghO0qzVzpFfnGrSdreMzB90Llu0G1n/SyU6KgV/tkAuxUz3xdR+O5Onse -4sOLxx+xy5zUTld78+mAu0fcJzIGGGKOZRtY9Xm9G6ZGUaJuWFQK267ebLcIWtbt -tsWGzg5Diw6Of92j/3PUPak2HGYTAY6oiQbB9ueunexjTwbVYibWEY+DXsmyCdus -RNFq6WBu/75lzW8Iw+hoerffNV7HX5l8EmjxTHIqnebyEYItj4nvUEtrTf9QnCIE -NkZvKElEfI2hwHFGw/4RHTryyCwtfvN7K1weAbqhedVowa7/Y+roQ/4LML6oiAdd -0XvEVSTRtc8iW+W64A4L7bByLXYza4Qca1VW0WxQEonmbI7ok6DbUTr1/ZBmG7SF -JKU60Img/9vmU/M4a7C81vR7LAD+uVSEBTObrqZfq5lXLfbHihaED4dafJjUSQYd -bRfelDvkiJYt3tiVUKyw1ZmNYRz00QMjqP4vFW0bWYfFpm4G9CfIKLOPLYVVOIkC -gcEA1bfUvCr6nQLu0PBFkeWiNUr5B+YkcRnaCgDqPxy6bWFKcfXxFp0ZaUn3YE8t -kYU0g+f3mslfCE+Ea6NqXLBYiP5fC4ZKp9CxUCCkPc8IEnO2Qjjitc4Fzgus/Neg -ZyyqTIB2AiPOBrQpUj4F02n8Uq+zwOz1M+pQpFq2KILbFcw7vB9A+q0oq9IIgA79 -XjqUHuUI7mHA1E8/o4wP7njQYZvij69IKpTImBbR8dim+c8p8W/iJtcsi5tRMKJC -TikfAoHBANL53eKCIDZXV7TArT2qO5ElOdpWarDyPgQk1Kp1/zOeVHqNPUYm3pUJ -3c6euJ0AbyVLXSMuywACfLSn/CeT2awiLdaT9VbdiHqf0fc3JkT9RPbRJzKGU4jv -lsjELsFoK/VqGsiyNgJ+1qH/cDspkq0T2AZqzojhFR3RgLqydFlNnK7h9HTdhLQE -ptuX8uNxecG9Dhuem10rNyo2wsejYp2H/pp+xdXSQbVXTMcNZLCf5+PwQyOBcu/4 -xZ69SXvxuQKBwHASYYV3SEqT4iZBCUpzz/cAZM0Gm6+Pre43Ap+r8fsv3LkcDpwc -L9otxoKDotK/toVBXG+YD7Ss9jT30evfMd5M/gxuFLIh1wm8xEQBUCtU8IuThxdt -fU/KgaFpdZ0FKBzL9JY0rnKHgW+2RJnT51R4/Ns7p23c/wLg6ssdq8OngN+qyAls -Snqnvntg4O7L6ya0VlWjf1oWYLnj1erpAeVduJY+haqGsRZISTn4sb8oKseIM2DI -z3w6K35EaF7SYwKBwAgQZuvGf4a0NfUBvF10CUzV+G0hCnjy+W3dHofZdMS7U41P -orzwtWdK9kB0jqfpoVXymH/ExRud/LhHB3tXymUq9UeTZjhIxXOycisZ34uZhq5M -dkwXGFBOoBvSa9sWqT97K8TqEuhrgra0dcy9HQ8iiApOwNA4kj8Oh/Ya5hmXvtps -ZpSk9vKOjvYry6vn5XiDSmvB1zxrb3NcnntVAmr1DMVI/TTcEYjl6lhdabDam9l5 -kIdl5kfH4X3czFdBeQKBwQCl+buKLSsW+EtHdKozIprwbwLsmhlzxr69iRwZ0A6O -VAF+C/WyxfWsvBtSOnabUfVtRyV64UyKiV3d53rkRPWxpXXAsag55vVWg/ZTV7dF -f+BC8TWZiLDYyYw/nJf6u6wj9phYQtQXkGZsTsQU5jpQRRrN4fy8WeFF0mtLEor0 -9kZPhlcQw2kkGkZ5o2LwmmL6/A6Cq14M4mGdzmT4vXT53U94qy+/9uPVP8zXzmhv -xGBczcuY4kJxhNAZt+ecORg= ------END PRIVATE KEY----- \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-bob.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-bob.pem deleted file mode 100644 index eaf333749ac8..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-bob.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQC8VtryJR7kgw/b -Qxi0pIo06ufj9Y84who8mqm7Jw7XyTXwTkC9WjnGpa1bTfkR09u9ZAGiiF2gdhWK -aeVdpo2395w+vnFS+EDR6W3G/IRd4/rYsFH5BpTC9ghrpGf12KF2Lq9rSJMNpuwM -4o3eFPS6BRGsSVtWHs5OzhkJ2w+v0ZYir64FWxJoUFVR+pjIx9LXi/oip6kvKAUW -WjARy7p684DtJHM4c68aIWJ3RjI84iJFajsksf9vy0mUT/fIvNOggaWAregZzyUC -lQpyQK8FruLhsLAmnnm6v2BgIzVYBzh31yNtAo0+IFqPgkBH4CSwn50adWf+sWf9 -hUUY2U+HOOBYeQvsMqvfwdrOmGstJGzVjO3A+Z2EmOpwQ/hR535LPaH+ayozGaQG -zvDpih+sb8Bf16KQ2TrLRHhYATvw1iJ9P+UzK1/4MVz0Pe4CLnQdyau8jSTw3ZoM -tLSCrUN13h25c+G4xs5oQ01CdRrrmFmCZsJBrrTC7b154oQPaCcCAwEAAQKCAYA3 -8Uqek1UeRinPFJqBicA9oKrZhOaSUuFtL/mLjDn18Z/SbcQAGo0/ypV4IeyCFZ5X -Hp33s66vy3Nxhivpid/djbB82J+bp/7E+yOu7/G3W9D2TUVEBWbplCADLTXuCfdH -ve1AHjID45mI6ECAf1QD7x6FQNLe/U6FjOVWDpoVvNjuG6XClH8eugHl1FxOg0BG -4x6c7ZUs2yPLtGVD2/Jbs/mPNvz8fyw7REBVsmWYvQi1+qxvpWl8wPLtmWU93Iwy -ZJS5bk03HLofEa9vJaqePePXNHlID54KSxqXkudjDP0hCMlz5peSkSECIw9aG7n/ -YIpsssDoLuRu3rsqa5k7NUalaxn+GfwHHqcooAkJ3P2OczIQk65G8tRh/FUu4fH6 -p7y5L4/uCyJfMx1xEc4owzWMq8jQu1zTd3YsoEXoFjzaOvrMyGdEdS6VeN272Ms9 -9WrSBfbkMSkjFzs8DmEOxIgcG5BmnYsyTj/YeBDyqdOLXGG+7w2dHY0pGPEwobEC -gcEA96QG1BYm93xHTnMpvX/K2Q/4HoM+zgURcGRsutJyl0XgsjYrgtme7ZEt1Ocr -tMnZEgcM82vH7W2VqDBt63L0UePz2TQ8ksZuvhU1+I+m6DunMv/8WaH6LjSdEhf4 -QcruMI5lBho8kMniCLQ3FJ1NWvBJDYqdkkzveFe+d76eKnUCp+aOPvIp8b1YnQhf -9kEZ5kj+QEeIP2OCxJKNEL4OxxiW+AnXPmxbSmceQudhU3DMS7ssbmuqxNOX2bGT -ohR3AoHBAMKyYOKQNeUCRK8DPIHl5OfGt4MzidAMnRXjZjufYHHb0dMBFnfVNOtS -YKodIPXcmEmMmuS/qD+BekP7vKFmSC6BocIz/cMrLhr6bwuv28psH4w5SLOge3ML -xfTe4AtjhRUhzY0FhtHYIfZ1Fvn2B9q3DpcRjUGP6PHvDUpV0pciHPQriqck3Ak/ -lzuor/kMf7g1GKv6sW/ErY15BHQdreDmuHoTT7HewXiYQEuWC0BPZVMhKkYcRRVe -zA2/xdSl0QKBwH5Uzir51g0g+Ft8AooqnDfZnrEqMSr2iOv2P0WbQFwNiNBBNDc/ -f4UgL+pRuAk1g6hfsmqnzElCQzuW4Sxg62SvTmG69T/HPQ15Upwn5HTKocQxtPGg -4TkbHBnz4nDl7lcU/VJ8pfMTGl1oWkUI7kJ7HDltQm81EmEpGgCKXLO6F43B30Ub -UuyAtKj/cbz1fxnsmM//7fCwejB/trkYy/8jezi0BHtS2ct+CYEF+q+PdpkXtlXq -VSat7uReY7smhwKBwGUkwcwxWq4ztXat2bspsPbvdtpD5e4c/2YlkjCbJUFBDwns -4L00F5/AVdNJ+pt/E+Xxk24OWttS6i9zEZVPS2RNl7kJxWSkg/kj7JhHrYjFDsaL -48bdMgScMyR4vC9ube6CdwQxwM42173vKhaEx6PJwVydk6JhnoRAyci6OQKmiCAY -lpkqPtzXGju18GUTJy6M9toxDAiiRf7exX+FA5V4hpF0gsxGin+ZqJ0bv5CTLnI3 -Inz1prXpX1wtKhrEYQKBwQDhhAyU+4yq56xpFtKhPEboU0X2rVNIP32PKIolvSx7 -8hDXtO1Bf6irAWSX3ogJRQ0YZnIwEty/GX6cLHixR3bxJXu7L+v3x2DOaC6AHICR -KbOMvvZ8aZX12wx08R0pEuoqeNcITHpecuI2laNlVRjVhMhd9s02LF3rdUcR8YAU -YG5RaF45LuNKQ4C8yYEs2YwFe1C3ekW6hmCVZpnk+ZzN3vz7Ot5F7POXxJ5pFgbd -uBCmMzRwdnkbVgNRQtJqiSY= ------END PRIVATE KEY----- \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-carol.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-carol.pem deleted file mode 100644 index c39d353080fa..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-private-carol.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCZTCQlanDwGQlj -oUoop15RNm1C5nRh57ZWx/KO1IAVqHA+eHhCRCD5nTS3CBR4yck3r7pZk7PFTCLV -HxDQSJjxCR7eMNZHmbIe6vV86YGr1DYLHUfejS5zP1D7sp0UVx5KKO9mypVnRmao -LrwgVmHBcLyVx6/KMT6SzK22M0l+uqwtQ5DhcNHBR6NrMY7DaPgxugZGdptDf7wp -/4CsrQv7p9aN3eKOrOaKdEuN6zX7Slw5xPmmuh1QqwK26p++SpJW6x6yEHm3Lp1n -ZLrIqUYB2xkS7bE3tn28HKyOcCEvEODnriy9POZeG4M2pClZNcK58H5CnquOyZAR -a7P0A1KD2I9Tvbh4Yinh+W0wkzbVEQzuwf4CwJofj+uYqQ6nN+GJekicYNPCmOkd -nlRkV1NtsWaOqBrr8E3ODLuIvoXgXytlB5aooZE50cUuURhb0VztB6GBY5urX/Qr -jIpgmGIgvHHdkGD1+FoZNxD+lr60V2kOKtjEsAOL8otexdYCQi8CAwEAAQKCAYAX -Rlk37mjxUKEn+vrFy2Dllww7G777MnFxIZsxQRBQDtU5QZSOCHFdP2zGhrfc/ajL -B1JAJs3PnIIEC7BuKeeWs8TNBWszTyGdJ3KtoXe/p/urRP8pdL64XsT5QiLZThTa -O6GFi309mJHI4oz2TUc7AIE8LdJXMjzcOwP7kDPjcDeTXm573A5CIzyrmh2hNlR1 -8Fbi5z/RnW5AjiYc5t/VOKQPFsDphVTcxqVwtGVAybHQ+kLRTpwEKG1uv5a7ZJox -FDIzypyZ2djYPcN0UiXwAQlEFk3X6f0zfca6s4p+GY3wuNt04FKbSChN7mlqS2/y -zYDkB/FF1AG0jgkaQnM3DhtedzF1DWS2n9DmFQTL71qE33CYQ7NIeHV5XwRLs+UE -uOJiDqNj2mQENy/Gz7kEcRVwC/ZoUDc7Ff9tT6Mp6P2DOKLkTnQhy/qHnym7WGMd -wKSuQAAoX1J4nxGGfJXJU63mw2+ZGv2FDspN8mnvpnsa+K5Awi1satY5mRuTtlEC -gcEA1RGgsB3EMX+BAZz9VNxlFQue3x5TeVQtdI7jPqfS10IA44EKvq+Jtq+Vhedh -Tt0CXuTp/1jDsG41uVVkOLVtb0zpdPU9wBpS/vW66vElVEN1/A/pncL/W5Gk1WKM -2tCqy/pQYuSvp5LodA+9h7RrH/LLeuVtgJYPGwBZgA0IkOQf9ERa4Zb4y7r9Wxr5 -mft9gzxwfA+ABYFgg1/C4C7ZLKGnYqnHVZ8knpPQ1OJyAieIaDk9Pr9G0cIsBdrY -7F/nAoHBALgvbKIS8cgrlMi/eNwi/c4Ka9w6AETgQ+jDxCL7wbCcUaYYUq/OHWqt -ATLF3x4Bqkiam5cMCUCCfQCKzGsE9iaHjgx6dSOhHLy8KxM8BssBuDeAWCtIwirn -B0v2i4oScq29ZXL10mpxMkrzYeNuy8I+9MlfA3IptVS92Ts8+SrFjwHE5ySlts7f -wfGT/eGSZxBNMP9x8RWnIy9i4YywrIDFrXTSAjIkKMbg15p3w9/B4WH3yPSrlbee -ON1SWYnieQKBwA/qDCV7Y68Kxfj339gZyhcDUi96FWQHjImbLo/8evwG+wgDGGlb -tR6HCNUCjjsUuNubn3qqB5vC75JTNXQi4PSiOwG4W49gp/hRJE/uCQq9Ky/ThHB7 -IxWU0En73WdulIM9xAlO6WvLxj78+fwL1sCBIv+f6UjkbGZo5UZyMsSXtWdKP7t/ -yj91TP0kC6abqSlHN2OcyMQggLPvUB/sf94ciOMOqUvU4ihSBBQSgc91YnPeapuo -L8L1DNK3IOKu2wKBwQCF3AGpsPcDsZjKZU3jP5MSAcTuI0E6zXrNWinb+viD8Lfu -bNL7bmM2ulGaovBLEI6/gY3+swKuXaeXk0iJput2iSIVs5IauOb3zBHKQt/0/mTy -AVcS7igfohRNgvta95lc578pU7r/HRGeI1BLSKTRoYuqJmGMwP9GfUu5n9C+4yHR -+Va77av0oe6EMjhbjllVIaSwohDC2dvafpTJ+UUFuaUct9xcsKbqE1LespgqsO+q -kbj9CQ8gid9uCoGEkpECgcEAw7LZYA5QMGihwLrDWi18nHMsCo0Z2Z1NntFoIUal -SgSxgzEH2LEamswToHI5I+TBvMQGKx7FY6y3Mbc3PnQQzeoSr8FNC9ExR/c0/bCm -8mm0TmwOQPPEoQVSquLOxGdP6wr/Rhzy+D5uec3Ml0K4FH8nMkZ+FvPT6akBthE9 -tMoXAfkx5NgjO16ZB8LBbowzWpydKTK/A//czHBqStYbQNcTpS4o6fCSkt6bw2f5 -19ga5B0NGXbh/GQ89wQttlbX ------END PRIVATE KEY----- \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-alice.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-alice.pem deleted file mode 100644 index 5f9a737f31e6..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-alice.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQwFADASMRAwDgYDVQQDEwdzLW5v -ZGUwMB4XDTI0MDExOTAzNTk1NVoXDTM0MDExOTAzNTk1NlowEjEQMA4GA1UEAxMH -cy1ub2RlMDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALAhZY0R2eAn -QXXSjpZTlL8rVM3l/mCPpSvMXVwwnDAxuczXoiinPgS+p6NkxPAzLGI9+VBTWiDL -hiJEQhBvmpK2+JvbpF9mlsvNb5oq/vKfpuZQYqfIxobDer0OSnO2JooT/mMG/frH -qgKod2LYWoeZgza2tsv+HDBBdh2S+ZxihoNOFpMHAuee8x5EWkAHMa6k+lrrbx0c -JcaIObU0ylfXZlCJlbZw3kjUST7Ld0xzJISRQyE+TP0XQ0DBcMXX5NxtXv1a6vLE -asvjzxFpa39ulSow4dZmq7BV+s6Wz7e7U1sOhk9CcRTpcs7Iaks9lZZhqpBI9AfT -GAZgPEbg+RKZ7jrsx7aYC3xTPSTvU4jk0X8CwWXjJe1AaWZEj7AZsV6qzHwxrgSH -z+L+sKe8a0yMBMThbhQsYTb6Fm1yLDsnEL2iFna/++TsRTarQK6OOpeZEk3s4lnS -bZRKZyJaUVNW6cknOIxIXac/CEF9EzYgwpCUu+fBYZ8tQ4jYGBbmZwIDAQABo2cw -ZTASBgNVHRMBAf8ECDAGAQH/AgEBMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCvppktifICQ9gt+ZCrI -GNskYJbYMA0GCSqGSIb3DQEBDAUAA4IBgQCDCwtZIlBCUtTdZpiPo8cS4W0r5j3X -xnX1PLVMEo5bgO2N1Kt86ipUW23wo6+RT5KAgRQ68SQ57w4SgQ/OypPZy+ya6hI4 -7c7Oyrm3yb7vHcb/oWSYh4TLa+J5YHchg3sIFusvLR68tDptdXnlVZqfNXbvbe+m -5ucTtPWe49zeOy9nz2DT9EbLzEJzvEbasqaYM9y9rIfB7A+5VKOztt2BsoPztgGn -wvCGHe1MegT/dV+f1t5vvyRmXGQNqguB6KzuWZ224xAAZd0kxIE7wbHFrcFyI8ki -eLQZBBQeNiRIO2P6KSCFqe0tK1PsxNAF4ApzTCfR46T8jJELT2JlBX5C390ywmb8 -Qqt1GV7gfSQO1noQNoy5rm8wi7GZUfMGY6ZsTlIuzLK27i2Ov6HBApxi0gnEeV/U -GtpRhTQHWBFlfU7ylPcVv76T+YjRi/Mh5i5mLdGfzRZESXbXdEIr2vNvH512dusa -o2DbRUiIOLJtAel7FOXrXKvHsz/HmSSPgMg= ------END CERTIFICATE----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-bob.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-bob.pem deleted file mode 100644 index fcf3421d4dbe..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-bob.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQwFADASMRAwDgYDVQQDEwdzLW5v -ZGUxMB4XDTI0MDExOTAzNTk1NloXDTM0MDExOTAzNTk1NlowEjEQMA4GA1UEAxMH -cy1ub2RlMTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALxW2vIlHuSD -D9tDGLSkijTq5+P1jzjCGjyaqbsnDtfJNfBOQL1aOcalrVtN+RHT271kAaKIXaB2 -FYpp5V2mjbf3nD6+cVL4QNHpbcb8hF3j+tiwUfkGlML2CGukZ/XYoXYur2tIkw2m -7Azijd4U9LoFEaxJW1Yezk7OGQnbD6/RliKvrgVbEmhQVVH6mMjH0teL+iKnqS8o -BRZaMBHLunrzgO0kczhzrxohYndGMjziIkVqOySx/2/LSZRP98i806CBpYCt6BnP -JQKVCnJArwWu4uGwsCaeebq/YGAjNVgHOHfXI20CjT4gWo+CQEfgJLCfnRp1Z/6x -Z/2FRRjZT4c44Fh5C+wyq9/B2s6Yay0kbNWM7cD5nYSY6nBD+FHnfks9of5rKjMZ -pAbO8OmKH6xvwF/XopDZOstEeFgBO/DWIn0/5TMrX/gxXPQ97gIudB3Jq7yNJPDd -mgy0tIKtQ3XeHblz4bjGzmhDTUJ1GuuYWYJmwkGutMLtvXnihA9oJwIDAQABo2cw -ZTASBgNVHRMBAf8ECDAGAQH/AgEBMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFGBToF/0Wh9x83dMShj/ -lni1voLLMA0GCSqGSIb3DQEBDAUAA4IBgQBfHAv3YMQr7DXw7KYclwDMWdMnC6OY -jReRUnMzgpryEAheHFUsTzsY83++gR7M+nGGAuClE9rhJ0VwiAVZSM+wXeHqCvWc -nhGxYkStXO//iJ4V90D1xYN452Bwv2MVNAfGcpNkDWpYU/T6NWbBwM3MaB1BJpog -3CiZHnbGyrGWnY5rCNEcxPt0x9tZypMf6tlfsrz10XSuzKfXYUl8gDAzSUVXBQf/ -nPM+8qgJCtrbzweTZOioDEFS8bJoRdhrISTwUqM5aWCyCb00PXRWgwYrs2Yx6h/2 -F5H0RRdEe8RlQnZzNRaKBgx5gKq1ZqVLo9PKKgUIOK1DOYKM97NEMJholk5iUYRF -dtX0hYIelWTSws5fooPMR9CmKJv/sQ1lUAK5R+sZGpR6HXs/XzJ2foxzBJtYBK5G -+TdXnGkwqZni6+2JhIpY2b9lyU3NrJaG8kQXb7BM/f8kmmTev+P4MXlJxkVGurgq -JpSc04LzDtFx3VeUB60TqjRWq/lAebOfwyQ= ------END CERTIFICATE----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-carol.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-carol.pem deleted file mode 100644 index f7399ce46306..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/s-public-carol.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQwFADASMRAwDgYDVQQDEwdzLW5v -ZGUyMB4XDTI0MDExOTAzNTk1NloXDTM0MDExOTAzNTk1NlowEjEQMA4GA1UEAxMH -cy1ub2RlMjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJlMJCVqcPAZ -CWOhSiinXlE2bULmdGHntlbH8o7UgBWocD54eEJEIPmdNLcIFHjJyTevulmTs8VM -ItUfENBImPEJHt4w1keZsh7q9XzpgavUNgsdR96NLnM/UPuynRRXHkoo72bKlWdG -ZqguvCBWYcFwvJXHr8oxPpLMrbYzSX66rC1DkOFw0cFHo2sxjsNo+DG6BkZ2m0N/ -vCn/gKytC/un1o3d4o6s5op0S43rNftKXDnE+aa6HVCrArbqn75KklbrHrIQebcu -nWdkusipRgHbGRLtsTe2fbwcrI5wIS8Q4OeuLL085l4bgzakKVk1wrnwfkKeq47J -kBFrs/QDUoPYj1O9uHhiKeH5bTCTNtURDO7B/gLAmh+P65ipDqc34Yl6SJxg08KY -6R2eVGRXU22xZo6oGuvwTc4Mu4i+heBfK2UHlqihkTnRxS5RGFvRXO0HoYFjm6tf -9CuMimCYYiC8cd2QYPX4Whk3EP6WvrRXaQ4q2MSwA4vyi17F1gJCLwIDAQABo2cw -ZTASBgNVHRMBAf8ECDAGAQH/AgEBMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKi2jUNl/87jYgPRnkzu -wxMe9zFpMA0GCSqGSIb3DQEBDAUAA4IBgQA5cYrIfc/jTMplm7vAHEq4kl2os0MU -8Vkfdw56afhPwjPRhI3OeWKuFyhTFfOYrO3oleYEj/eFkwSl6wXIeUKrbwMWxtuL -Qyqax1YLIZMkwSDDQTAYkAUJgsY4MTeYEJz1X9eXKH7KD+fNTEd0j7C4qXaow1ms -OJYIkUVyoqx4i1szfWxw0X04LRxjL/u5TF34fOqOKWQekvVPSvKUH9kuTnQsPyG2 -XXr4rQ4Ea5IMCPkSdpr7kZhhSze38P9QdCfBYibIk46+zeInfD4tKQ3R8sGYHK0M -kx6INe5onCc/zvlTizJ0v8XwhjAU7vWcKJV/PxB94FzHdM0r8a6pfK0HXkup2mM1 -FMghPPyC7XW+Fl1aRMwWWnEW9+1zoA3SbEgEje4hkIbUVCQL7bKOobV8FLqACw+F -uIcwILb+oZJeKof2py/NnxOdNBp2EYc7lEEv+1zwxmkAVGjQ3I/pHkD2gr1ZHsxm -+IYcd5yAGvwyd69a2QtaqbBx4c18kuT97tQ= ------END CERTIFICATE----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-alice.tss b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-alice.tss deleted file mode 100644 index f52048a80fac..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-alice.tss +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -CORRUPTEDTSSKEY ------END PRIVATE KEY----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-bob.tss b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-bob.tss deleted file mode 100644 index f539b89a98a8..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-bob.tss +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -AXtqyKZ3PTuSk3t4BY1Gcxiw6SifnuXzU1zO/Zmh7NUZ ------END PRIVATE KEY----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-carol.tss b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-carol.tss deleted file mode 100644 index c23ec154637b..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-corrupt-tss-key/t-private-carol.tss +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -AZMIRAUnkI9m6nh1vWWGD84hg76ZLxGyNSLk/c1WS5QS ------END PRIVATE KEY----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-alice.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-alice.pem deleted file mode 100644 index 92b7e50d3f8d..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-alice.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCwIWWNEdngJ0F1 -0o6WU5S/K1TN5f5gj6UrzF1cMJwwMbnM16Iopz4EvqejZMTwMyxiPflQU1ogy4Yi -REIQb5qStvib26RfZpbLzW+aKv7yn6bmUGKnyMaGw3q9DkpztiaKE/5jBv36x6oC -qHdi2FqHmYM2trbL/hwwQXYdkvmcYoaDThaTBwLnnvMeRFpABzGupPpa628dHCXG -iDm1NMpX12ZQiZW2cN5I1Ek+y3dMcySEkUMhPkz9F0NAwXDF1+TcbV79WuryxGrL -488RaWt/bpUqMOHWZquwVfrOls+3u1NbDoZPQnEU6XLOyGpLPZWWYaqQSPQH0xgG -YDxG4PkSme467Me2mAt8Uz0k71OI5NF/AsFl4yXtQGlmRI+wGbFeqsx8Ma4Eh8/i -/rCnvGtMjATE4W4ULGE2+hZtciw7JxC9ohZ2v/vk7EU2q0CujjqXmRJN7OJZ0m2U -SmciWlFTVunJJziMSF2nPwhBfRM2IMKQlLvnwWGfLUOI2BgW5mcCAwEAAQKCAYAD -Sp3k5HEghO0qzVzpFfnGrSdreMzB90Llu0G1n/SyU6KgV/tkAuxUz3xdR+O5Onse -4sOLxx+xy5zUTld78+mAu0fcJzIGGGKOZRtY9Xm9G6ZGUaJuWFQK267ebLcIWtbt -tsWGzg5Diw6Of92j/3PUPak2HGYTAY6oiQbB9ueunexjTwbVYibWEY+DXsmyCdus -RNFq6WBu/75lzW8Iw+hoerffNV7HX5l8EmjxTHIqnebyEYItj4nvUEtrTf9QnCIE -NkZvKElEfI2hwHFGw/4RHTryyCwtfvN7K1weAbqhedVowa7/Y+roQ/4LML6oiAdd -0XvEVSTRtc8iW+W64A4L7bByLXYza4Qca1VW0WxQEonmbI7ok6DbUTr1/ZBmG7SF -JKU60Img/9vmU/M4a7C81vR7LAD+uVSEBTObrqZfq5lXLfbHihaED4dafJjUSQYd -bRfelDvkiJYt3tiVUKyw1ZmNYRz00QMjqP4vFW0bWYfFpm4G9CfIKLOPLYVVOIkC -gcEA1bfUvCr6nQLu0PBFkeWiNUr5B+YkcRnaCgDqPxy6bWFKcfXxFp0ZaUn3YE8t -kYU0g+f3mslfCE+Ea6NqXLBYiP5fC4ZKp9CxUCCkPc8IEnO2Qjjitc4Fzgus/Neg -ZyyqTIB2AiPOBrQpUj4F02n8Uq+zwOz1M+pQpFq2KILbFcw7vB9A+q0oq9IIgA79 -XjqUHuUI7mHA1E8/o4wP7njQYZvij69IKpTImBbR8dim+c8p8W/iJtcsi5tRMKJC -TikfAoHBANL53eKCIDZXV7TArT2qO5ElOdpWarDyPgQk1Kp1/zOeVHqNPUYm3pUJ -3c6euJ0AbyVLXSMuywACfLSn/CeT2awiLdaT9VbdiHqf0fc3JkT9RPbRJzKGU4jv -lsjELsFoK/VqGsiyNgJ+1qH/cDspkq0T2AZqzojhFR3RgLqydFlNnK7h9HTdhLQE -ptuX8uNxecG9Dhuem10rNyo2wsejYp2H/pp+xdXSQbVXTMcNZLCf5+PwQyOBcu/4 -xZ69SXvxuQKBwHASYYV3SEqT4iZBCUpzz/cAZM0Gm6+Pre43Ap+r8fsv3LkcDpwc -L9otxoKDotK/toVBXG+YD7Ss9jT30evfMd5M/gxuFLIh1wm8xEQBUCtU8IuThxdt -fU/KgaFpdZ0FKBzL9JY0rnKHgW+2RJnT51R4/Ns7p23c/wLg6ssdq8OngN+qyAls -Snqnvntg4O7L6ya0VlWjf1oWYLnj1erpAeVduJY+haqGsRZISTn4sb8oKseIM2DI -z3w6K35EaF7SYwKBwAgQZuvGf4a0NfUBvF10CUzV+G0hCnjy+W3dHofZdMS7U41P -orzwtWdK9kB0jqfpoVXymH/ExRud/LhHB3tXymUq9UeTZjhIxXOycisZ34uZhq5M -dkwXGFBOoBvSa9sWqT97K8TqEuhrgra0dcy9HQ8iiApOwNA4kj8Oh/Ya5hmXvtps -ZpSk9vKOjvYry6vn5XiDSmvB1zxrb3NcnntVAmr1DMVI/TTcEYjl6lhdabDam9l5 -kIdl5kfH4X3czFdBeQKBwQCl+buKLSsW+EtHdKozIprwbwLsmhlzxr69iRwZ0A6O -VAF+C/WyxfWsvBtSOnabUfVtRyV64UyKiV3d53rkRPWxpXXAsag55vVWg/ZTV7dF -f+BC8TWZiLDYyYw/nJf6u6wj9phYQtQXkGZsTsQU5jpQRRrN4fy8WeFF0mtLEor0 -9kZPhlcQw2kkGkZ5o2LwmmL6/A6Cq14M4mGdzmT4vXT53U94qy+/9uPVP8zXzmhv -xGBczcuY4kJxhNAZt+ecORg= ------END PRIVATE KEY----- \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-bob.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-bob.pem deleted file mode 100644 index eaf333749ac8..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-bob.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQC8VtryJR7kgw/b -Qxi0pIo06ufj9Y84who8mqm7Jw7XyTXwTkC9WjnGpa1bTfkR09u9ZAGiiF2gdhWK -aeVdpo2395w+vnFS+EDR6W3G/IRd4/rYsFH5BpTC9ghrpGf12KF2Lq9rSJMNpuwM -4o3eFPS6BRGsSVtWHs5OzhkJ2w+v0ZYir64FWxJoUFVR+pjIx9LXi/oip6kvKAUW -WjARy7p684DtJHM4c68aIWJ3RjI84iJFajsksf9vy0mUT/fIvNOggaWAregZzyUC -lQpyQK8FruLhsLAmnnm6v2BgIzVYBzh31yNtAo0+IFqPgkBH4CSwn50adWf+sWf9 -hUUY2U+HOOBYeQvsMqvfwdrOmGstJGzVjO3A+Z2EmOpwQ/hR535LPaH+ayozGaQG -zvDpih+sb8Bf16KQ2TrLRHhYATvw1iJ9P+UzK1/4MVz0Pe4CLnQdyau8jSTw3ZoM -tLSCrUN13h25c+G4xs5oQ01CdRrrmFmCZsJBrrTC7b154oQPaCcCAwEAAQKCAYA3 -8Uqek1UeRinPFJqBicA9oKrZhOaSUuFtL/mLjDn18Z/SbcQAGo0/ypV4IeyCFZ5X -Hp33s66vy3Nxhivpid/djbB82J+bp/7E+yOu7/G3W9D2TUVEBWbplCADLTXuCfdH -ve1AHjID45mI6ECAf1QD7x6FQNLe/U6FjOVWDpoVvNjuG6XClH8eugHl1FxOg0BG -4x6c7ZUs2yPLtGVD2/Jbs/mPNvz8fyw7REBVsmWYvQi1+qxvpWl8wPLtmWU93Iwy -ZJS5bk03HLofEa9vJaqePePXNHlID54KSxqXkudjDP0hCMlz5peSkSECIw9aG7n/ -YIpsssDoLuRu3rsqa5k7NUalaxn+GfwHHqcooAkJ3P2OczIQk65G8tRh/FUu4fH6 -p7y5L4/uCyJfMx1xEc4owzWMq8jQu1zTd3YsoEXoFjzaOvrMyGdEdS6VeN272Ms9 -9WrSBfbkMSkjFzs8DmEOxIgcG5BmnYsyTj/YeBDyqdOLXGG+7w2dHY0pGPEwobEC -gcEA96QG1BYm93xHTnMpvX/K2Q/4HoM+zgURcGRsutJyl0XgsjYrgtme7ZEt1Ocr -tMnZEgcM82vH7W2VqDBt63L0UePz2TQ8ksZuvhU1+I+m6DunMv/8WaH6LjSdEhf4 -QcruMI5lBho8kMniCLQ3FJ1NWvBJDYqdkkzveFe+d76eKnUCp+aOPvIp8b1YnQhf -9kEZ5kj+QEeIP2OCxJKNEL4OxxiW+AnXPmxbSmceQudhU3DMS7ssbmuqxNOX2bGT -ohR3AoHBAMKyYOKQNeUCRK8DPIHl5OfGt4MzidAMnRXjZjufYHHb0dMBFnfVNOtS -YKodIPXcmEmMmuS/qD+BekP7vKFmSC6BocIz/cMrLhr6bwuv28psH4w5SLOge3ML -xfTe4AtjhRUhzY0FhtHYIfZ1Fvn2B9q3DpcRjUGP6PHvDUpV0pciHPQriqck3Ak/ -lzuor/kMf7g1GKv6sW/ErY15BHQdreDmuHoTT7HewXiYQEuWC0BPZVMhKkYcRRVe -zA2/xdSl0QKBwH5Uzir51g0g+Ft8AooqnDfZnrEqMSr2iOv2P0WbQFwNiNBBNDc/ -f4UgL+pRuAk1g6hfsmqnzElCQzuW4Sxg62SvTmG69T/HPQ15Upwn5HTKocQxtPGg -4TkbHBnz4nDl7lcU/VJ8pfMTGl1oWkUI7kJ7HDltQm81EmEpGgCKXLO6F43B30Ub -UuyAtKj/cbz1fxnsmM//7fCwejB/trkYy/8jezi0BHtS2ct+CYEF+q+PdpkXtlXq -VSat7uReY7smhwKBwGUkwcwxWq4ztXat2bspsPbvdtpD5e4c/2YlkjCbJUFBDwns -4L00F5/AVdNJ+pt/E+Xxk24OWttS6i9zEZVPS2RNl7kJxWSkg/kj7JhHrYjFDsaL -48bdMgScMyR4vC9ube6CdwQxwM42173vKhaEx6PJwVydk6JhnoRAyci6OQKmiCAY -lpkqPtzXGju18GUTJy6M9toxDAiiRf7exX+FA5V4hpF0gsxGin+ZqJ0bv5CTLnI3 -Inz1prXpX1wtKhrEYQKBwQDhhAyU+4yq56xpFtKhPEboU0X2rVNIP32PKIolvSx7 -8hDXtO1Bf6irAWSX3ogJRQ0YZnIwEty/GX6cLHixR3bxJXu7L+v3x2DOaC6AHICR -KbOMvvZ8aZX12wx08R0pEuoqeNcITHpecuI2laNlVRjVhMhd9s02LF3rdUcR8YAU -YG5RaF45LuNKQ4C8yYEs2YwFe1C3ekW6hmCVZpnk+ZzN3vz7Ot5F7POXxJ5pFgbd -uBCmMzRwdnkbVgNRQtJqiSY= ------END PRIVATE KEY----- \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-carol.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-carol.pem deleted file mode 100644 index c39d353080fa..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-private-carol.pem +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCZTCQlanDwGQlj -oUoop15RNm1C5nRh57ZWx/KO1IAVqHA+eHhCRCD5nTS3CBR4yck3r7pZk7PFTCLV -HxDQSJjxCR7eMNZHmbIe6vV86YGr1DYLHUfejS5zP1D7sp0UVx5KKO9mypVnRmao -LrwgVmHBcLyVx6/KMT6SzK22M0l+uqwtQ5DhcNHBR6NrMY7DaPgxugZGdptDf7wp -/4CsrQv7p9aN3eKOrOaKdEuN6zX7Slw5xPmmuh1QqwK26p++SpJW6x6yEHm3Lp1n -ZLrIqUYB2xkS7bE3tn28HKyOcCEvEODnriy9POZeG4M2pClZNcK58H5CnquOyZAR -a7P0A1KD2I9Tvbh4Yinh+W0wkzbVEQzuwf4CwJofj+uYqQ6nN+GJekicYNPCmOkd -nlRkV1NtsWaOqBrr8E3ODLuIvoXgXytlB5aooZE50cUuURhb0VztB6GBY5urX/Qr -jIpgmGIgvHHdkGD1+FoZNxD+lr60V2kOKtjEsAOL8otexdYCQi8CAwEAAQKCAYAX -Rlk37mjxUKEn+vrFy2Dllww7G777MnFxIZsxQRBQDtU5QZSOCHFdP2zGhrfc/ajL -B1JAJs3PnIIEC7BuKeeWs8TNBWszTyGdJ3KtoXe/p/urRP8pdL64XsT5QiLZThTa -O6GFi309mJHI4oz2TUc7AIE8LdJXMjzcOwP7kDPjcDeTXm573A5CIzyrmh2hNlR1 -8Fbi5z/RnW5AjiYc5t/VOKQPFsDphVTcxqVwtGVAybHQ+kLRTpwEKG1uv5a7ZJox -FDIzypyZ2djYPcN0UiXwAQlEFk3X6f0zfca6s4p+GY3wuNt04FKbSChN7mlqS2/y -zYDkB/FF1AG0jgkaQnM3DhtedzF1DWS2n9DmFQTL71qE33CYQ7NIeHV5XwRLs+UE -uOJiDqNj2mQENy/Gz7kEcRVwC/ZoUDc7Ff9tT6Mp6P2DOKLkTnQhy/qHnym7WGMd -wKSuQAAoX1J4nxGGfJXJU63mw2+ZGv2FDspN8mnvpnsa+K5Awi1satY5mRuTtlEC -gcEA1RGgsB3EMX+BAZz9VNxlFQue3x5TeVQtdI7jPqfS10IA44EKvq+Jtq+Vhedh -Tt0CXuTp/1jDsG41uVVkOLVtb0zpdPU9wBpS/vW66vElVEN1/A/pncL/W5Gk1WKM -2tCqy/pQYuSvp5LodA+9h7RrH/LLeuVtgJYPGwBZgA0IkOQf9ERa4Zb4y7r9Wxr5 -mft9gzxwfA+ABYFgg1/C4C7ZLKGnYqnHVZ8knpPQ1OJyAieIaDk9Pr9G0cIsBdrY -7F/nAoHBALgvbKIS8cgrlMi/eNwi/c4Ka9w6AETgQ+jDxCL7wbCcUaYYUq/OHWqt -ATLF3x4Bqkiam5cMCUCCfQCKzGsE9iaHjgx6dSOhHLy8KxM8BssBuDeAWCtIwirn -B0v2i4oScq29ZXL10mpxMkrzYeNuy8I+9MlfA3IptVS92Ts8+SrFjwHE5ySlts7f -wfGT/eGSZxBNMP9x8RWnIy9i4YywrIDFrXTSAjIkKMbg15p3w9/B4WH3yPSrlbee -ON1SWYnieQKBwA/qDCV7Y68Kxfj339gZyhcDUi96FWQHjImbLo/8evwG+wgDGGlb -tR6HCNUCjjsUuNubn3qqB5vC75JTNXQi4PSiOwG4W49gp/hRJE/uCQq9Ky/ThHB7 -IxWU0En73WdulIM9xAlO6WvLxj78+fwL1sCBIv+f6UjkbGZo5UZyMsSXtWdKP7t/ -yj91TP0kC6abqSlHN2OcyMQggLPvUB/sf94ciOMOqUvU4ihSBBQSgc91YnPeapuo -L8L1DNK3IOKu2wKBwQCF3AGpsPcDsZjKZU3jP5MSAcTuI0E6zXrNWinb+viD8Lfu -bNL7bmM2ulGaovBLEI6/gY3+swKuXaeXk0iJput2iSIVs5IauOb3zBHKQt/0/mTy -AVcS7igfohRNgvta95lc578pU7r/HRGeI1BLSKTRoYuqJmGMwP9GfUu5n9C+4yHR -+Va77av0oe6EMjhbjllVIaSwohDC2dvafpTJ+UUFuaUct9xcsKbqE1LespgqsO+q -kbj9CQ8gid9uCoGEkpECgcEAw7LZYA5QMGihwLrDWi18nHMsCo0Z2Z1NntFoIUal -SgSxgzEH2LEamswToHI5I+TBvMQGKx7FY6y3Mbc3PnQQzeoSr8FNC9ExR/c0/bCm -8mm0TmwOQPPEoQVSquLOxGdP6wr/Rhzy+D5uec3Ml0K4FH8nMkZ+FvPT6akBthE9 -tMoXAfkx5NgjO16ZB8LBbowzWpydKTK/A//czHBqStYbQNcTpS4o6fCSkt6bw2f5 -19ga5B0NGXbh/GQ89wQttlbX ------END PRIVATE KEY----- \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-alice.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-alice.pem deleted file mode 100644 index 5f9a737f31e6..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-alice.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQwFADASMRAwDgYDVQQDEwdzLW5v -ZGUwMB4XDTI0MDExOTAzNTk1NVoXDTM0MDExOTAzNTk1NlowEjEQMA4GA1UEAxMH -cy1ub2RlMDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALAhZY0R2eAn -QXXSjpZTlL8rVM3l/mCPpSvMXVwwnDAxuczXoiinPgS+p6NkxPAzLGI9+VBTWiDL -hiJEQhBvmpK2+JvbpF9mlsvNb5oq/vKfpuZQYqfIxobDer0OSnO2JooT/mMG/frH -qgKod2LYWoeZgza2tsv+HDBBdh2S+ZxihoNOFpMHAuee8x5EWkAHMa6k+lrrbx0c -JcaIObU0ylfXZlCJlbZw3kjUST7Ld0xzJISRQyE+TP0XQ0DBcMXX5NxtXv1a6vLE -asvjzxFpa39ulSow4dZmq7BV+s6Wz7e7U1sOhk9CcRTpcs7Iaks9lZZhqpBI9AfT -GAZgPEbg+RKZ7jrsx7aYC3xTPSTvU4jk0X8CwWXjJe1AaWZEj7AZsV6qzHwxrgSH -z+L+sKe8a0yMBMThbhQsYTb6Fm1yLDsnEL2iFna/++TsRTarQK6OOpeZEk3s4lnS -bZRKZyJaUVNW6cknOIxIXac/CEF9EzYgwpCUu+fBYZ8tQ4jYGBbmZwIDAQABo2cw -ZTASBgNVHRMBAf8ECDAGAQH/AgEBMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCvppktifICQ9gt+ZCrI -GNskYJbYMA0GCSqGSIb3DQEBDAUAA4IBgQCDCwtZIlBCUtTdZpiPo8cS4W0r5j3X -xnX1PLVMEo5bgO2N1Kt86ipUW23wo6+RT5KAgRQ68SQ57w4SgQ/OypPZy+ya6hI4 -7c7Oyrm3yb7vHcb/oWSYh4TLa+J5YHchg3sIFusvLR68tDptdXnlVZqfNXbvbe+m -5ucTtPWe49zeOy9nz2DT9EbLzEJzvEbasqaYM9y9rIfB7A+5VKOztt2BsoPztgGn -wvCGHe1MegT/dV+f1t5vvyRmXGQNqguB6KzuWZ224xAAZd0kxIE7wbHFrcFyI8ki -eLQZBBQeNiRIO2P6KSCFqe0tK1PsxNAF4ApzTCfR46T8jJELT2JlBX5C390ywmb8 -Qqt1GV7gfSQO1noQNoy5rm8wi7GZUfMGY6ZsTlIuzLK27i2Ov6HBApxi0gnEeV/U -GtpRhTQHWBFlfU7ylPcVv76T+YjRi/Mh5i5mLdGfzRZESXbXdEIr2vNvH512dusa -o2DbRUiIOLJtAel7FOXrXKvHsz/HmSSPgMg= ------END CERTIFICATE----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-bob.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-bob.pem deleted file mode 100644 index fcf3421d4dbe..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-bob.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQwFADASMRAwDgYDVQQDEwdzLW5v -ZGUxMB4XDTI0MDExOTAzNTk1NloXDTM0MDExOTAzNTk1NlowEjEQMA4GA1UEAxMH -cy1ub2RlMTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALxW2vIlHuSD -D9tDGLSkijTq5+P1jzjCGjyaqbsnDtfJNfBOQL1aOcalrVtN+RHT271kAaKIXaB2 -FYpp5V2mjbf3nD6+cVL4QNHpbcb8hF3j+tiwUfkGlML2CGukZ/XYoXYur2tIkw2m -7Azijd4U9LoFEaxJW1Yezk7OGQnbD6/RliKvrgVbEmhQVVH6mMjH0teL+iKnqS8o -BRZaMBHLunrzgO0kczhzrxohYndGMjziIkVqOySx/2/LSZRP98i806CBpYCt6BnP -JQKVCnJArwWu4uGwsCaeebq/YGAjNVgHOHfXI20CjT4gWo+CQEfgJLCfnRp1Z/6x -Z/2FRRjZT4c44Fh5C+wyq9/B2s6Yay0kbNWM7cD5nYSY6nBD+FHnfks9of5rKjMZ -pAbO8OmKH6xvwF/XopDZOstEeFgBO/DWIn0/5TMrX/gxXPQ97gIudB3Jq7yNJPDd -mgy0tIKtQ3XeHblz4bjGzmhDTUJ1GuuYWYJmwkGutMLtvXnihA9oJwIDAQABo2cw -ZTASBgNVHRMBAf8ECDAGAQH/AgEBMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFGBToF/0Wh9x83dMShj/ -lni1voLLMA0GCSqGSIb3DQEBDAUAA4IBgQBfHAv3YMQr7DXw7KYclwDMWdMnC6OY -jReRUnMzgpryEAheHFUsTzsY83++gR7M+nGGAuClE9rhJ0VwiAVZSM+wXeHqCvWc -nhGxYkStXO//iJ4V90D1xYN452Bwv2MVNAfGcpNkDWpYU/T6NWbBwM3MaB1BJpog -3CiZHnbGyrGWnY5rCNEcxPt0x9tZypMf6tlfsrz10XSuzKfXYUl8gDAzSUVXBQf/ -nPM+8qgJCtrbzweTZOioDEFS8bJoRdhrISTwUqM5aWCyCb00PXRWgwYrs2Yx6h/2 -F5H0RRdEe8RlQnZzNRaKBgx5gKq1ZqVLo9PKKgUIOK1DOYKM97NEMJholk5iUYRF -dtX0hYIelWTSws5fooPMR9CmKJv/sQ1lUAK5R+sZGpR6HXs/XzJ2foxzBJtYBK5G -+TdXnGkwqZni6+2JhIpY2b9lyU3NrJaG8kQXb7BM/f8kmmTev+P4MXlJxkVGurgq -JpSc04LzDtFx3VeUB60TqjRWq/lAebOfwyQ= ------END CERTIFICATE----- diff --git a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-carol.pem b/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-carol.pem deleted file mode 100644 index f7399ce46306..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/resources/com/swirlds/platform/crypto/EnhancedKeyStoreLoader/enhanced-valid-no-tss-key/s-public-carol.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBjCCAm6gAwIBAgIBATANBgkqhkiG9w0BAQwFADASMRAwDgYDVQQDEwdzLW5v -ZGUyMB4XDTI0MDExOTAzNTk1NloXDTM0MDExOTAzNTk1NlowEjEQMA4GA1UEAxMH -cy1ub2RlMjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJlMJCVqcPAZ -CWOhSiinXlE2bULmdGHntlbH8o7UgBWocD54eEJEIPmdNLcIFHjJyTevulmTs8VM -ItUfENBImPEJHt4w1keZsh7q9XzpgavUNgsdR96NLnM/UPuynRRXHkoo72bKlWdG -ZqguvCBWYcFwvJXHr8oxPpLMrbYzSX66rC1DkOFw0cFHo2sxjsNo+DG6BkZ2m0N/ -vCn/gKytC/un1o3d4o6s5op0S43rNftKXDnE+aa6HVCrArbqn75KklbrHrIQebcu -nWdkusipRgHbGRLtsTe2fbwcrI5wIS8Q4OeuLL085l4bgzakKVk1wrnwfkKeq47J -kBFrs/QDUoPYj1O9uHhiKeH5bTCTNtURDO7B/gLAmh+P65ipDqc34Yl6SJxg08KY -6R2eVGRXU22xZo6oGuvwTc4Mu4i+heBfK2UHlqihkTnRxS5RGFvRXO0HoYFjm6tf -9CuMimCYYiC8cd2QYPX4Whk3EP6WvrRXaQ4q2MSwA4vyi17F1gJCLwIDAQABo2cw -ZTASBgNVHRMBAf8ECDAGAQH/AgEBMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFKi2jUNl/87jYgPRnkzu -wxMe9zFpMA0GCSqGSIb3DQEBDAUAA4IBgQA5cYrIfc/jTMplm7vAHEq4kl2os0MU -8Vkfdw56afhPwjPRhI3OeWKuFyhTFfOYrO3oleYEj/eFkwSl6wXIeUKrbwMWxtuL -Qyqax1YLIZMkwSDDQTAYkAUJgsY4MTeYEJz1X9eXKH7KD+fNTEd0j7C4qXaow1ms -OJYIkUVyoqx4i1szfWxw0X04LRxjL/u5TF34fOqOKWQekvVPSvKUH9kuTnQsPyG2 -XXr4rQ4Ea5IMCPkSdpr7kZhhSze38P9QdCfBYibIk46+zeInfD4tKQ3R8sGYHK0M -kx6INe5onCc/zvlTizJ0v8XwhjAU7vWcKJV/PxB94FzHdM0r8a6pfK0HXkup2mM1 -FMghPPyC7XW+Fl1aRMwWWnEW9+1zoA3SbEgEje4hkIbUVCQL7bKOobV8FLqACw+F -uIcwILb+oZJeKof2py/NnxOdNBp2EYc7lEEv+1zwxmkAVGjQ3I/pHkD2gr1ZHsxm -+IYcd5yAGvwyd69a2QtaqbBx4c18kuT97tQ= ------END CERTIFICATE----- diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleStateRoot.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleStateRoot.java index 3ff4420437fb..945d4458c2ed 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleStateRoot.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleStateRoot.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -830,7 +830,9 @@ public void commit() { * @param stateKey the state key */ public void remove(String stateKey) { - stateMetadata.remove(stateKey); + if (!Map.of().equals(stateMetadata)) { + stateMetadata.remove(stateKey); + } kvInstances.remove(stateKey); singletonInstances.remove(stateKey); queueInstances.remove(stateKey); diff --git a/version.txt b/version.txt index a60476bfe1c7..145a83724a73 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.58.0 +0.59.0-SNAPSHOT