From baa73295d53bc1a7285c76d5729668e2a7a35dc0 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:22:53 -0600 Subject: [PATCH] chore: Enable block signing test and add Metrics (#16632) Signed-off-by: Neeharika-Sompalli --- .../main/java/com/hedera/node/app/Hedera.java | 2 +- .../node/app/tss/TssBaseServiceImpl.java | 6 +- .../com/hedera/node/app/tss/TssMetrics.java | 50 ++++++++++++ .../handlers/TssShareSignatureHandler.java | 23 +++++- .../app/workflows/handle/HandleWorkflow.java | 3 - .../hedera/node/app/tss/TssMetricsTest.java | 15 ++++ .../TssShareSignatureHandlerTest.java | 7 +- .../node/config/data/NetworkAdminConfig.java | 2 +- .../embedded/RepeatableEmbeddedHedera.java | 10 ++- .../hedera/embedded/fakes/FakeEvent.java | 7 ++ .../embedded/fakes/FakeTssBaseService.java | 2 +- .../hedera/embedded/fakes/FakeTssLibrary.java | 4 +- .../services/bdd/spec/utilops/TssVerbs.java | 19 ++++- .../bdd/spec/utilops/tss/RekeyScenarioOp.java | 76 ++++++++++++++++--- .../RepeatableIntegrationTests.java | 25 +++++- 15 files changed, 221 insertions(+), 30 deletions(-) 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 190414996d52..16e6a9ee4055 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 @@ -384,7 +384,7 @@ public Hedera( new SignatureExpanderImpl(), new SignatureVerifierImpl(CryptographyHolder.get())), this, - bootstrapConfigProvider::getConfiguration, + () -> configProvider.getConfiguration(), () -> daggerApp.networkInfo().selfNodeInfo()); tssBaseService = tssBaseServiceFactory.apply(appContext); contractServiceImpl = new ContractServiceImpl(appContext); 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 313d169d63ae..3d8c3b37eded 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 @@ -62,7 +62,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -86,14 +85,14 @@ public class TssBaseServiceImpl implements TssBaseService { private final TssHandlers tssHandlers; private final TssSubmissions tssSubmissions; private final Executor tssLibraryExecutor; - private final ExecutorService signingExecutor; + private final Executor signingExecutor; private final TssKeysAccessor tssKeysAccessor; private final TssDirectoryAccessor tssDirectoryAccessor; private final AppContext appContext; public TssBaseServiceImpl( @NonNull final AppContext appContext, - @NonNull final ExecutorService signingExecutor, + @NonNull final Executor signingExecutor, @NonNull final Executor submissionExecutor, @NonNull final TssLibrary tssLibrary, @NonNull final Executor tssLibraryExecutor, @@ -197,7 +196,6 @@ public void setCandidateRoster(@NonNull final Roster candidateRoster, @NonNull f @Override public void requestLedgerSignature(final byte[] messageHash, final Instant lastUsedConsensusTime) { requireNonNull(messageHash); - // (TSS-FUTURE) Initiate an asynchronous process of creating a ledger signature final var mockSignature = noThrowSha384HashOf(messageHash); CompletableFuture.runAsync( () -> { 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 index be8f60990fad..7a0858c5299f 100644 --- 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 @@ -63,6 +63,22 @@ public class TssMetrics { 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 @@ -78,6 +94,8 @@ 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); } /** @@ -156,6 +174,12 @@ public void updateAggregationTime(final long aggregationTime) { 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 @@ -175,6 +199,19 @@ public void updateAggregationTime(final long aggregationTime) { 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 */ @@ -190,4 +227,17 @@ public long getAggregationTime() { 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/handlers/TssShareSignatureHandler.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssShareSignatureHandler.java index f63b5278ce0d..1eddd93b311a 100644 --- 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 @@ -28,6 +28,7 @@ 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.TssLibrary; import com.hedera.node.app.tss.api.TssShareId; import com.hedera.node.app.tss.api.TssShareSignature; @@ -37,6 +38,7 @@ 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; @@ -59,17 +61,20 @@ public class TssShareSignatureHandler implements TransactionHandler { 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) { + @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 @@ -94,8 +99,20 @@ public void preHandle(@NonNull final PreHandleContext context) throws PreCheckEx // 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 ledgerSignature = tssLibrary.aggregateSignatures( - tssShareSignatures.stream().toList()); + final var aggregationStart = instantSource.instant(); + final PairingSignature 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.signature().toBytes()); } 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 0cdaa8f0fc95..4413691cfc00 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 @@ -382,9 +382,6 @@ private HandleOutput execute(@NonNull final UserTxn userTxn) { // (FUTURE) Once all genesis setup is done via dispatch, remove this method systemSetup.externalizeInitSideEffects( userTxn.tokenContextImpl(), exchangeRateManager.exchangeRates()); - // Set the genesis roster in state - final var rosterStore = writableRosterStoreFactory.getStore(WritableRosterStore.class); - rosterStore.putActiveRoster(networkInfo.roster(), 1L); } else if (userTxn.type() == POST_UPGRADE_TRANSACTION) { final var writableStakingInfoStore = new WritableStakingInfoStore(userTxn.stack().getWritableStates(TokenService.NAME)); 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 index 1a6f94a9f45c..3909db560f1e 100644 --- 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 @@ -58,6 +58,21 @@ public void aggregationTimeGetUpdated() { 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(); 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 index 0b035bcabbeb..1b79772adbd7 100644 --- 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 @@ -30,6 +30,7 @@ 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.TssLibrary; import com.hedera.node.app.tss.api.TssParticipantDirectory; import com.hedera.node.app.tss.api.TssPrivateShare; @@ -68,6 +69,9 @@ public class TssShareSignatureHandlerTest { @Mock private PreHandleContext context; + @Mock + private TssMetrics tssMetrics; + private TssShareSignatureHandler handler; private static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(new byte[] {1}); @@ -89,7 +93,8 @@ public class TssShareSignatureHandlerTest { void setUp() { given(rosterKeyMaterialAccessor.accessTssKeys()).willReturn(TSS_KEYS); given(instantSource.instant()).willReturn(Instant.ofEpochSecond(1_234_567L)); - handler = new TssShareSignatureHandler(tssLibrary, instantSource, rosterKeyMaterialAccessor, tssBaseService); + handler = new TssShareSignatureHandler( + tssLibrary, instantSource, rosterKeyMaterialAccessor, tssBaseService, tssMetrics); } @Test 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 849b0ec32136..ac2f211981c3 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 @@ -43,5 +43,5 @@ public record NetworkAdminConfig( @ConfigProperty(defaultValue = "throttles.json") String upgradeThrottlesFile, @ConfigProperty(defaultValue = "application-override.properties") String upgradePropertyOverridesFile, @ConfigProperty(defaultValue = "api-permission-override.properties") String upgradePermissionOverridesFile, - @ConfigProperty(defaultValue = "TssMessage,TssVote") @NetworkProperty + @ConfigProperty(defaultValue = "TssMessage,TssVote,TssShareSignature") @NetworkProperty HederaFunctionalitySet nodeTransactionsAllowList) {} 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 07487a80b978..be752a3f03d7 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 @@ -20,6 +20,7 @@ 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; @@ -93,13 +94,13 @@ public TransactionResponse submit( new FakeEvent(nodeId, time.now(), semanticVersion, createAppPayloadWrapper(payload)); } if (response.getNodeTransactionPrecheckCode() == OK) { - handleNextRound(); + handleNextRound(false); // If handling this transaction scheduled TSS work, do it synchronously as well while (tssBaseService.hasTssSubmission()) { platform.lastCreatedEvent = null; tssBaseService.executeNextTssSubmission(); if (platform.lastCreatedEvent != null) { - handleNextRound(); + handleNextRound(true); } } } @@ -120,8 +121,11 @@ public long lastRoundNo() { return platform.lastRoundNo(); } - private void handleNextRound() { + private void handleNextRound(boolean skipsSignatureTxn) { hedera.onPreHandle(platform.lastCreatedEvent, state); + if (skipsSignatureTxn && platform.lastCreatedEvent.function() == HederaFunctionality.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/FakeEvent.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeEvent.java index 4ce9b6ae8a34..ba0685f62979 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeEvent.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeEvent.java @@ -18,10 +18,12 @@ import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.platform.event.EventCore; import com.hedera.hapi.util.HapiUtils; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.hedera.services.bdd.junit.support.translators.inputs.TransactionParts; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.Transaction; @@ -96,4 +98,9 @@ public EventCore getEventCore() { public Bytes getSignature() { return FAKE_SHA_384_SIGNATURE; } + + @NonNull + public HederaFunctionality function() { + return TransactionParts.from(transaction.getApplicationTransaction()).function(); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java index 15e1e7399b09..9d698b6ef482 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java @@ -86,7 +86,7 @@ public enum Signing { DELEGATE } - private Signing signing = Signing.FAKE; + private Signing signing = Signing.DELEGATE; private boolean ignoreRequests = false; public FakeTssBaseService(@NonNull final AppContext appContext) { 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 index d9eeeab489c5..77dea1e55ed9 100644 --- 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 @@ -44,6 +44,8 @@ public class FakeTssLibrary implements TssLibrary { private static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(new byte[] {1}); private static final PairingPrivateKey PRIVATE_KEY = new PairingPrivateKey(new FakeFieldElement(BigInteger.valueOf(42L)), SIGNATURE_SCHEMA); + public static final PairingSignature FAKE_SIGNATURE = + new PairingSignature(new FakeGroupElement(BigInteger.valueOf(1L)), SIGNATURE_SCHEMA); public interface DirectoryAssertion { void assertExpected(@NonNull TssParticipantDirectory directory) throws AssertionError; @@ -181,7 +183,7 @@ public boolean verifySignature( @NonNull @Override public PairingSignature aggregateSignatures(@NonNull final List partialSignatures) { - return new PairingSignature(new FakeGroupElement(BigInteger.valueOf(0L)), SIGNATURE_SCHEMA); + return FAKE_SIGNATURE; } @NonNull 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 e52f1c270187..a3a3452b380e 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 @@ -83,7 +83,24 @@ public static RekeyScenarioOp rekeyingScenario( @NonNull final RekeyScenarioOp.DabEdits dabEdits, @NonNull final LongUnaryOperator nodeStakes, @NonNull final LongFunction tssMessageSims) { - return new RekeyScenarioOp(dabEdits, nodeStakes, 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); } /** diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java index 6dbab7e4994e..6e2c1aac1340 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java @@ -21,6 +21,7 @@ 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_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; @@ -35,6 +36,8 @@ 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.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.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; @@ -62,6 +65,9 @@ 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.common.RosterStateId; +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; @@ -129,6 +135,7 @@ private record NonEmbeddedTssMessage(long nodeId, @NonNull TssMessage tssMessage private int actualMessages = 0; private int expectedVotes = NA; private int expectedMessages = NA; + private int actualTssSignatures = 0; static { try { @@ -159,7 +166,7 @@ public enum TssMessageSim { /** * 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 nodesToDelete the set of node IDs to delete * @param numNodesToCreate the number of nodes to create */ public record DabEdits(@NonNull Set nodesToDelete, int numNodesToCreate) { @@ -176,6 +183,12 @@ public record DabEdits(@NonNull Set nodesToDelete, int numNodesToCreate) { 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. */ @@ -185,22 +198,26 @@ public record DabEdits(@NonNull Set nodesToDelete, int numNodesToCreate) { 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 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 LongFunction tssMessageSims, + @NonNull final BlockSigningType blockSigningType) { this.dabEdits = requireNonNull(dabEdits); this.nodeStakes = requireNonNull(nodeStakes); this.tssMessageSims = requireNonNull(tssMessageSims); + this.blockSigningType = requireNonNull(blockSigningType); } @Override @@ -231,12 +248,14 @@ public boolean test(@NonNull final Block block) throws AssertionError { (expectedMessages == NA) ? "?" : expectedMessages); observeInteractionsIn(block); log.info( - "Post-block#{}, votes={}/{}, messages={}/{}", + "Post-block#{}, votes={}/{}, messages={}/{}, tssSignatures={}", blockNo, actualVotes, (expectedVotes == NA) ? "?" : expectedVotes, actualMessages, - (expectedMessages == NA) ? "?" : expectedMessages); + (expectedMessages == NA) ? "?" : expectedMessages, + actualTssSignatures); + boolean votesAndMessagesMatch = true; if (expectedMessages != NA && expectedVotes != NA) { if (actualMessages > expectedMessages) { Assertions.fail( @@ -245,9 +264,14 @@ public boolean test(@NonNull final Block block) throws AssertionError { if (actualVotes > expectedVotes) { Assertions.fail("Too many votes submitted, expected " + expectedVotes + " but got " + actualVotes); } - return (actualMessages == expectedMessages) && (actualVotes == expectedVotes); + votesAndMessagesMatch = (actualMessages == expectedMessages) && (actualVotes == expectedVotes); } - return false; + 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) { @@ -271,13 +295,27 @@ private void observeInteractionsIn(@NonNull final Block block) { } } } + final var blockProof = block.items().getLast().blockProofOrThrow(); + if (blockProof + .blockSignature() + .equals(Bytes.wrap(FAKE_SIGNATURE.signature().toBytes()))) { + actualTssSignatures++; + } } /** * Returns a stream of operations that enable TSS feature flags. */ private Stream enableTss() { - return Stream.of(overriding("tss.keyCandidateRoster", "true")); + 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")); + } } /** @@ -377,6 +415,19 @@ private SpecOperation extractRosterMetadata() { final var state = spec.embeddedStateOrThrow(); final var hedera = spec.repeatableEmbeddedHederaOrThrow(); final var roundNo = hedera.lastRoundNo(); + + final var writableStates = state.getWritableStates(RosterStateId.NAME); + final var rosterStore = new WritableRosterStore(writableStates); + final var activeEntries = + new ArrayList<>(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 activeRoster = requireNonNull(retrieveActive(state, roundNo)); sourceRosterHash = getActiveRosterHash(state, roundNo); @@ -454,4 +505,9 @@ private SpecOperation setActiveRosterKeyMaterial() { }); }); } + + 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/integration/RepeatableIntegrationTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableIntegrationTests.java index fde9139eda7f..1d2119f40967 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableIntegrationTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableIntegrationTests.java @@ -30,7 +30,10 @@ 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; @@ -106,7 +109,27 @@ Stream embeddedNodeVotesGivenThresholdValidMessages() { 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))); + 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); }