Skip to content

Commit

Permalink
feat: Add logic for TssMessageHandler for happy path (#16062)
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Moran <[email protected]>
Signed-off-by: Neeharika-Sompalli <[email protected]>
Signed-off-by: Miroslav Gatsanoga <[email protected]>
Co-authored-by: Thomas Moran <[email protected]>
Co-authored-by: Joseph S. <[email protected]>
Co-authored-by: Miroslav Gatsanoga <[email protected]>
  • Loading branch information
4 people authored Oct 23, 2024
1 parent adea711 commit 01b57ec
Show file tree
Hide file tree
Showing 30 changed files with 1,047 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@

package com.hedera.node.app.spi;

import static com.hedera.hapi.node.base.ResponseCodeEnum.FAIL_INVALID;

import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.spi.signatures.SignatureVerifier;
import com.swirlds.common.crypto.Signature;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.time.InstantSource;

Expand All @@ -37,34 +36,54 @@ interface Gossip {
* A {@link Gossip} that throws an exception indicating it should never have been used; for example,
* if the client code was running in a standalone mode.
*/
Gossip UNAVAILABLE_GOSSIP = body -> {
throw new IllegalArgumentException("" + FAIL_INVALID);
Gossip UNAVAILABLE_GOSSIP = new Gossip() {
@Override
public void submit(@NonNull final TransactionBody body) {
throw new IllegalStateException("Gossip is not available!");
}

@Override
public Signature sign(final byte[] ledgerId) {
throw new IllegalStateException("Gossip is not available!");
}
};

/**
* Attempts to submit the given transaction to the network.
*
* @param body the transaction to submit
* @throws IllegalStateException if the network is not active; the client should retry later
* @throws IllegalStateException if the network is not active; the client should retry later
* @throws IllegalArgumentException if body is invalid; so the client can retry immediately with a
* different transaction id if the exception's message is {@link ResponseCodeEnum#DUPLICATE_TRANSACTION}
* different transaction id if the exception's message is {@link ResponseCodeEnum#DUPLICATE_TRANSACTION}
*/
void submit(@NonNull TransactionBody body);

/**
* Signs the given bytes with the node's RSA key and returns the signature.
*
* @param bytes the bytes to sign
* @return the signature
*/
Signature sign(byte[] bytes);
}

/**
* The source of the current instant.
*
* @return the instant source
*/
InstantSource instantSource();

/**
* The signature verifier the application workflows will use.
*
* @return the signature verifier
*/
SignatureVerifier signatureVerifier();

/**
* The {@link Gossip} can be used to submit transactions to the network when it is active.
*
* @return the gossip interface
*/
Gossip gossip();
Expand Down
1 change: 1 addition & 0 deletions hedera-node/hedera-app-spi/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module com.hedera.node.app.spi {
requires transitive com.hedera.node.app.hapi.utils;
requires transitive com.hedera.node.hapi;
requires transitive com.swirlds.common;
requires transitive com.swirlds.config.api;
requires transitive com.swirlds.state.api;
requires transitive com.hedera.pbj.runtime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.hedera.node.app.fixtures.state.FakeState;
import com.hedera.node.app.services.AppContextImpl;
import com.hedera.node.app.spi.signatures.SignatureVerifier;
import com.hedera.node.app.tss.PlaceholderTssLibrary;
import com.hedera.node.app.tss.TssBaseServiceImpl;
import com.hedera.node.config.ConfigProvider;
import com.hedera.pbj.runtime.OneOf;
Expand Down Expand Up @@ -117,6 +118,8 @@ public static void main(String... args) throws Exception {
private final TssBaseServiceImpl tssBaseService = new TssBaseServiceImpl(
new AppContextImpl(Instant::now, fakeSignatureVerifier(), UNAVAILABLE_GOSSIP),
ForkJoinPool.commonPool(),
ForkJoinPool.commonPool(),
new PlaceholderTssLibrary(),
ForkJoinPool.commonPool());
private final BlockStreamManagerImpl subject = new BlockStreamManagerImpl(
NoopBlockItemWriter::new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.hedera.node.app.fixtures.state.FakeState;
import com.hedera.node.app.services.AppContextImpl;
import com.hedera.node.app.spi.signatures.SignatureVerifier;
import com.hedera.node.app.tss.PlaceholderTssLibrary;
import com.hedera.node.app.tss.TssBaseServiceImpl;
import com.hedera.node.config.ConfigProvider;
import com.hedera.node.config.data.BlockStreamConfig;
Expand Down Expand Up @@ -93,6 +94,8 @@ public class StandaloneRoundManagement {
private final TssBaseServiceImpl tssBaseService = new TssBaseServiceImpl(
new AppContextImpl(Instant::now, fakeSignatureVerifier(), UNAVAILABLE_GOSSIP),
ForkJoinPool.commonPool(),
ForkJoinPool.commonPool(),
new PlaceholderTssLibrary(),
ForkJoinPool.commonPool());
private final BlockStreamManagerImpl subject = new BlockStreamManagerImpl(
NoopBlockItemWriter::new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
import com.swirlds.common.constructable.RuntimeConstructable;
import com.swirlds.common.crypto.CryptographyHolder;
import com.swirlds.common.crypto.Hash;
import com.swirlds.common.crypto.Signature;
import com.swirlds.common.notification.NotificationEngine;
import com.swirlds.common.platform.NodeId;
import com.swirlds.config.api.Configuration;
Expand Down Expand Up @@ -693,6 +694,11 @@ public void submit(@NonNull final TransactionBody body) {
}
}

@Override
public Signature sign(final byte[] ledgerId) {
return platform.sign(ledgerId);
}

/**
* Called to perform orderly close record streams.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

import com.hedera.node.app.services.OrderedServiceMigrator;
import com.hedera.node.app.services.ServicesRegistryImpl;
import com.hedera.node.app.tss.PlaceholderTssLibrary;
import com.hedera.node.app.tss.TssBaseServiceImpl;
import com.swirlds.base.time.Time;
import com.swirlds.common.constructable.ConstructableRegistry;
Expand Down Expand Up @@ -370,6 +371,11 @@ private static Hedera newHedera() {
ServicesRegistryImpl::new,
new OrderedServiceMigrator(),
InstantSource.system(),
appContext -> new TssBaseServiceImpl(appContext, ForkJoinPool.commonPool(), ForkJoinPool.commonPool()));
appContext -> new TssBaseServiceImpl(
appContext,
ForkJoinPool.commonPool(),
ForkJoinPool.commonPool(),
new PlaceholderTssLibrary(),
ForkJoinPool.commonPool()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.hedera.services.bdd.junit.hedera.embedded.fakes.tss;
package com.hedera.node.app.tss;

import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf;

Expand All @@ -35,7 +35,8 @@
import java.util.List;
import org.jetbrains.annotations.NotNull;

public class FakeTssLibrary implements TssLibrary {
public class PlaceholderTssLibrary implements TssLibrary {
public static final int DEFAULT_THRESHOLD = 10;
public static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(new byte[] {1});
private static final PairingPrivateKey AGGREGATED_PRIVATE_KEY =
new PairingPrivateKey(new FakeFieldElement(BigInteger.valueOf(42L)), SIGNATURE_SCHEMA);
Expand All @@ -45,14 +46,18 @@ public class FakeTssLibrary implements TssLibrary {
private final int threshold;
private byte[] message = new byte[0];

public FakeTssLibrary(int threshold) {
public PlaceholderTssLibrary(int threshold) {
if (threshold <= 0) {
throw new IllegalArgumentException("Invalid threshold: " + threshold);
}

this.threshold = threshold;
}

public PlaceholderTssLibrary() {
this(DEFAULT_THRESHOLD);
}

@NotNull
@Override
public TssMessage generateTssMessage(@NotNull TssParticipantDirectory tssParticipantDirectory) {
Expand Down Expand Up @@ -149,7 +154,7 @@ public boolean verifySignature(
}

// This method is not part of the TssLibrary interface, used for testing purposes
void setTestMessage(byte[] message) {
public void setTestMessage(byte[] message) {
this.message = message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import com.hedera.hapi.node.state.roster.Roster;
import com.hedera.node.app.spi.workflows.HandleContext;
import com.hedera.node.app.tss.handlers.TssHandlers;
import com.hedera.node.app.tss.stores.ReadableTssBaseStore;
import com.hedera.node.app.tss.stores.ReadableTssStoreImpl;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.swirlds.state.spi.Service;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand Down Expand Up @@ -63,7 +63,7 @@ default String getServiceName() {
* @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 ReadableTssBaseStore tssBaseStore);
Status getStatus(@NonNull Roster roster, @NonNull Bytes ledgerId, @NonNull ReadableTssStoreImpl tssBaseStore);

/**
* Adopts the given roster for TSS operations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
import javax.inject.Singleton;

@Singleton
@Component()
@Component(modules = {TssModule.class})
public interface TssBaseServiceComponent {
@Component.Factory
interface Factory {
TssBaseServiceComponent create(
@BindsInstance AppContext.Gossip gossip, @BindsInstance Executor submissionExecutor);
@BindsInstance AppContext.Gossip gossip,
@BindsInstance Executor submissionExecutor,
@BindsInstance @TssLibraryExecutor Executor libraryExecutor);
}

TssMessageHandler tssMessageHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@

import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf;
import static com.hedera.node.app.tss.TssBaseService.Status.PENDING_LEDGER_ID;
import static com.hedera.node.app.tss.handlers.TssUtils.computeTssParticipantDirectory;
import static com.hedera.node.app.tss.handlers.TssUtils.getTssMessages;
import static com.hedera.node.app.tss.handlers.TssUtils.validateTssMessages;
import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.state.roster.Roster;
import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody;
import com.hedera.node.app.spi.AppContext;
import com.hedera.node.app.spi.workflows.HandleContext;
import com.hedera.node.app.tss.api.TssLibrary;
import com.hedera.node.app.tss.api.TssPrivateShare;
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.stores.ReadableTssBaseStore;
import com.hedera.node.app.tss.stores.ReadableTssStoreImpl;
import com.hedera.node.config.data.TssConfig;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.swirlds.common.utility.CommonUtils;
import com.swirlds.platform.roster.RosterUtils;
import com.swirlds.platform.state.service.ReadableRosterStore;
import com.swirlds.state.spi.SchemaRegistry;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
Expand All @@ -39,6 +46,7 @@
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;
import org.apache.logging.log4j.LogManager;
Expand All @@ -59,6 +67,8 @@ public class TssBaseServiceImpl implements TssBaseService {
private final TssHandlers tssHandlers;
private final TssSubmissions tssSubmissions;
private final ExecutorService signingExecutor;
private final TssLibrary tssLibrary;
private final Executor tssLibraryExecutor;

/**
* The hash of the active roster being used to sign with the ledger private key.
Expand All @@ -69,12 +79,17 @@ public class TssBaseServiceImpl implements TssBaseService {
public TssBaseServiceImpl(
@NonNull final AppContext appContext,
@NonNull final ExecutorService signingExecutor,
@NonNull final Executor submissionExecutor) {
@NonNull final Executor submissionExecutor,
@NonNull final TssLibrary tssLibrary,
@NonNull final Executor tssLibraryExecutor) {
requireNonNull(appContext);
this.signingExecutor = requireNonNull(signingExecutor);
final var component = DaggerTssBaseServiceComponent.factory().create(appContext.gossip(), submissionExecutor);
final var component = DaggerTssBaseServiceComponent.factory()
.create(appContext.gossip(), submissionExecutor, tssLibraryExecutor);
tssHandlers = new TssHandlers(component.tssMessageHandler(), component.tssVoteHandler());
tssSubmissions = component.tssSubmissions();
this.tssLibrary = requireNonNull(tssLibrary);
this.tssLibraryExecutor = requireNonNull(tssLibraryExecutor);
}

@Override
Expand All @@ -87,7 +102,7 @@ public void registerSchemas(@NonNull final SchemaRegistry registry) {
public Status getStatus(
@NonNull final Roster roster,
@NonNull final Bytes ledgerId,
@NonNull final ReadableTssBaseStore tssBaseStore) {
@NonNull final ReadableTssStoreImpl tssBaseStore) {
requireNonNull(roster);
requireNonNull(ledgerId);
requireNonNull(tssBaseStore);
Expand Down Expand Up @@ -117,7 +132,58 @@ public void bootstrapLedgerId(
public void setCandidateRoster(@NonNull final Roster roster, @NonNull final HandleContext context) {
requireNonNull(roster);
// (TSS-FUTURE) https://github.com/hashgraph/hedera-services/issues/14748
tssSubmissions.submitTssMessage(TssMessageTransactionBody.DEFAULT, context);

// generate TSS messages based on the active roster and the candidate roster
final var tssStore = context.storeFactory().readableStore(ReadableTssStoreImpl.class);
final var maxSharesPerNode =
context.configuration().getConfigData(TssConfig.class).maxSharesPerNode();
final var sourceRoster =
context.storeFactory().readableStore(ReadableRosterStore.class).getActiveRoster();
final var activeRosterHash = RosterUtils.hash(sourceRoster).getBytes();
final var candidateRosterHash = RosterUtils.hash(roster).getBytes();
final var tssPrivateShares =
getTssPrivateShares(sourceRoster, maxSharesPerNode, tssStore, candidateRosterHash, context);
final var candidateRosterParticipantDirectory = computeTssParticipantDirectory(roster, maxSharesPerNode, (int)
context.networkInfo().selfNodeInfo().nodeId());

final AtomicInteger shareIndex = new AtomicInteger(0);
for (final var tssPrivateShare : tssPrivateShares) {
final var tssMsg = CompletableFuture.supplyAsync(
() -> tssLibrary.generateTssMessage(candidateRosterParticipantDirectory, tssPrivateShare),
tssLibraryExecutor)
.exceptionally(e -> {
log.error("Error generating tssMessage", e);
return null;
});
tssMsg.thenAccept(msg -> {
if (msg == null) {
return;
}
final var tssMessage = TssMessageTransactionBody.newBuilder()
.sourceRosterHash(activeRosterHash)
.targetRosterHash(candidateRosterHash)
.shareIndex(shareIndex.getAndAdd(1))
.tssMessage(Bytes.wrap(msg.bytes()))
.build();
tssSubmissions.submitTssMessage(tssMessage, context);
});
}
}

@NonNull
private List<TssPrivateShare> getTssPrivateShares(
@NonNull final Roster sourceRoster,
final long maxSharesPerNode,
@NonNull final ReadableTssStoreImpl tssStore,
@NonNull final Bytes candidateRosterHash,
final HandleContext context) {
final var selfId = (int) context.networkInfo().selfNodeInfo().nodeId();
final var activeRosterParticipantDirectory =
computeTssParticipantDirectory(sourceRoster, maxSharesPerNode, selfId);
final var validTssOps = validateTssMessages(
tssStore.getTssMessages(candidateRosterHash), activeRosterParticipantDirectory, tssLibrary);
final var validTssMessages = getTssMessages(validTssOps);
return tssLibrary.decryptPrivateShares(activeRosterParticipantDirectory, validTssMessages);
}

@Override
Expand Down
Loading

0 comments on commit 01b57ec

Please sign in to comment.