From acd08f7c8353554da9feaca31ef6459ee5b624bc Mon Sep 17 00:00:00 2001 From: Lazar Petrovic Date: Fri, 21 Feb 2025 15:17:42 +0100 Subject: [PATCH] feat: consensus can use birth rounds to determine ancient status (#17907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lazar Petrovic Signed-off-by: Timo Brandstätter Co-authored-by: Timo Brandstätter --- .../com/swirlds/platform/ConsensusImpl.java | 40 +++-- .../cli/GenesisPlatformStateCommand.java | 4 +- .../consensus/DefaultConsensusEngine.java | 2 +- .../platform/consensus/ConsensusRounds.java | 97 ++++++----- .../platform/consensus/ConsensusSnapshot.java | 6 +- .../consensus/RoundCalculationUtils.java | 21 --- .../platform/consensus/RoundElections.java | 34 +++- .../platform/consensus/SyntheticSnapshot.java | 32 ++-- .../swirlds/platform/event/PlatformEvent.java | 2 +- .../swirlds/platform/gui/GuiEventStorage.java | 29 +++- .../swirlds/platform/gui/SimpleLinker.java | 29 +++- .../hashgraph/HashgraphPictureOptions.java | 5 + .../internal/HashgraphGuiControls.java | 9 + .../hashgraph/internal/HashgraphPicture.java | 4 + .../swirlds/platform/internal/EventImpl.java | 32 +++- .../recovery/EventRecoveryWorkflow.java | 10 +- .../system/events/EventConstants.java | 2 + .../system/events/EventDescriptorWrapper.java | 32 ++++ .../platform/system/events/UnsignedEvent.java | 1 - .../consensus/RoundCalculationUtilsTest.java | 22 --- .../DefaultTransactionHandlerTests.java | 2 +- .../event/generator/GraphGenerator.java | 19 +-- .../generator/StandardGraphGenerator.java | 144 ++++++++-------- .../event/source/AbstractEventSource.java | 5 - .../test/consensus/ConsensusUtils.java | 42 ----- .../platform/test/consensus/TestIntake.java | 40 ++--- .../consensus/framework/ConsensusOutput.java | 22 ++- .../framework/ConsensusTestNode.java | 22 +++ .../framework/ConsensusTestOrchestrator.java | 24 ++- .../framework/OrchestratorBuilder.java | 18 +- .../validation/ConsensusRoundValidation.java | 20 +++ .../framework/validation/NoEventsLost.java | 6 +- .../event/emitter/AbstractEventEmitter.java | 8 - .../test/event/emitter/EventEmitter.java | 9 - .../platform/test/gui/TestGuiSource.java | 12 +- .../test/consensus/ConsensusTestArgs.java | 128 +++++--------- .../consensus/ConsensusTestDefinitions.java | 50 ++---- .../test/consensus/ConsensusTestParams.java | 7 +- .../test/consensus/ConsensusTestRunner.java | 38 +++-- .../test/consensus/ConsensusTests.java | 95 +++++------ .../test/consensus/GraphGeneratorTests.java | 42 +++-- .../consensus/IntakeAndConsensusTests.java | 157 +++--------------- 42 files changed, 624 insertions(+), 699 deletions(-) delete mode 100644 platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java index 7d1c5d2728e1..862b230fe35f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java @@ -34,6 +34,7 @@ import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.ConsensusMetrics; import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.util.MarkerFileWriter; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -221,11 +222,12 @@ public ConsensusImpl( this.rosterTotalWeight = RosterUtils.computeTotalWeight(roster); this.rosterIndicesMap = RosterUtils.toIndicesMap(roster); - this.rounds = new ConsensusRounds(config, roster); this.ancientMode = platformContext .getConfiguration() .getConfigData(EventConfig.class) .getAncientMode(); + this.rounds = new ConsensusRounds(config, ancientMode, roster); + this.noSuperMajorityLogger = new RateLimitedLogger(logger, platformContext.getTime(), Duration.ofMinutes(1)); this.noJudgeLogger = new RateLimitedLogger(logger, platformContext.getTime(), Duration.ofMinutes(1)); this.coinRoundLogger = new RateLimitedLogger(logger, platformContext.getTime(), Duration.ofMinutes(1)); @@ -345,7 +347,7 @@ && round(insertedEvent.getOtherParent()) == ConsensusConstants.ROUND_NEGATIVE_IN continue; } - if (insertedEvent.isConsensus() || rounds.isAncient(insertedEvent)) { + if (insertedEvent.isConsensus() || ancient(insertedEvent)) { insertedEvent.clearMetadata(); // all events that are consensus or ancient have a round of -infinity @@ -443,8 +445,7 @@ private boolean checkInitJudges(@NonNull final EventImpl event) { initJudges.judgeFound(event); logger.info( STARTUP.getMarker(), - "Found init judge %s, num remaining: {}" - .formatted(event.getBaseEvent().getDescriptor()), + "Found init judge %s, num remaining: {}".formatted(event.shortString()), initJudges::numMissingJudges); if (!initJudges.allJudgesFound()) { return false; @@ -463,6 +464,12 @@ private boolean checkInitJudges(@NonNull final EventImpl event) { e.setConsensus(true); e.setRecTimes(null); }); + // This value is normally updated when a round gets decided, but since we are starting from + // a snapshot, we need to set it here. + rounds.setConsensusRelevantGeneration(initJudges.getJudges().stream() + .map(EventImpl::getGeneration) + .min(Long::compareTo) + .orElse(EventConstants.FIRST_GENERATION)); initJudges = null; return true; @@ -636,8 +643,8 @@ private void logVote( logger.debug( CONSENSUS_VOTING.getMarker(), "Witness {} voted on {}. vote:{} type:{} diff:{}", - votingWitness, - candidateWitness.getWitness(), + votingWitness.shortString(), + candidateWitness.getWitness().shortString(), votingWitness.getVote(candidateWitness), votingType, diff); @@ -697,7 +704,7 @@ private List getStronglySeenInPreviousRound(final EventImpl event) { // Check for no judges or super majority conditions. checkJudges(judges, decidedRoundNumber); - // update the round and generation values since fame has been decided for a new round + // update the round and ancient threshold values since fame has been decided for a new round rounds.currentElectionDecided(); // all events that reach consensus during this method call, in consensus order @@ -723,18 +730,8 @@ private List getStronglySeenInPreviousRound(final EventImpl event) { } } - // Future work: prior to enabling a birth round based ancient mode, we need to use real values for - // previousRoundNonAncient and previousRoundNonExpired. This is currently a place holder. - final long previousRoundNonAncient = ConsensusConstants.ROUND_FIRST; - final long previousRoundNonExpired = ConsensusConstants.ROUND_FIRST; - - final long nonAncientThreshold = ancientMode.selectIndicator( - rounds.getMinGenerationNonAncient(), - Math.max(previousRoundNonAncient, decidedRoundNumber - config.roundsNonAncient() + 1)); - - final long nonExpiredThreshold = ancientMode.selectIndicator( - rounds.getMinRoundGeneration(), - Math.max(previousRoundNonExpired, decidedRoundNumber - config.roundsExpired() + 1)); + final long nonAncientThreshold = rounds.getAncientThreshold(); + final long nonExpiredThreshold = rounds.getExpiredThreshold(); return new ConsensusRound( roster, @@ -862,7 +859,7 @@ private void setConsensusOrder(@NonNull final Collection events) { } private boolean nonConsensusNonAncient(@NonNull final EventImpl e) { - return !e.isConsensus() && !rounds.isAncient(e); + return !e.isConsensus() && !ancient(e); } private @Nullable EventImpl timedStronglySeeP(@Nullable final EventImpl x, final long m) { @@ -918,11 +915,12 @@ private boolean witness(@NonNull final EventImpl x) { /** * Check if the event is ancient + * * @param x the event to check * @return true if the event is ancient */ private boolean ancient(@Nullable final EventImpl x) { - return x == null || x.getGeneration() < rounds.getMinGenerationNonAncient(); + return x == null || x.getAgeValue(ancientMode) < rounds.getAncientThreshold(); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index 8481abfa5682..095dfa706671 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -15,6 +15,7 @@ import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.consensus.SyntheticSnapshot; +import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.service.PlatformStateFacade; import com.swirlds.platform.state.service.WritableRosterStore; @@ -70,7 +71,8 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti stateFacade.bulkUpdateOf(reservedSignedState.get().getState(), v -> { System.out.printf("Replacing platform data %n"); v.setRound(PlatformStateAccessor.GENESIS_ROUND); - v.setSnapshot(SyntheticSnapshot.getGenesisSnapshot()); + v.setSnapshot(SyntheticSnapshot.getGenesisSnapshot( + configuration.getConfigData(EventConfig.class).getAncientMode())); // FUTURE WORK: remove once the AddressBook setters are deprecated and the fields are nullified. // For now, we have to keep these calls to ensure RosterRetriever won't fall back to using these values. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java index 9a006a44ab39..863388fe1c9e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/consensus/DefaultConsensusEngine.java @@ -113,7 +113,7 @@ public List addEvent(@NonNull final PlatformEvent event) { */ @Override public void outOfBandSnapshotUpdate(@NonNull final ConsensusSnapshot snapshot) { - final long ancientThreshold = snapshot.getMinimumGenerationNonAncient(roundsNonAncient); + final long ancientThreshold = snapshot.getAncientThreshold(roundsNonAncient); final EventWindow eventWindow = new EventWindow(snapshot.round(), ancientThreshold, ancientThreshold, ancientMode); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusRounds.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusRounds.java index 8c172e5bfcfe..8cab057dcb11 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusRounds.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusRounds.java @@ -4,6 +4,7 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; import com.swirlds.logging.legacy.LogMarker; +import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MinimumJudgeInfo; @@ -25,6 +26,8 @@ public class ConsensusRounds { private static final Logger logger = LogManager.getLogger(ConsensusRounds.class); /** consensus configuration */ private final ConsensusConfig config; + /** the ancient mode currently in use */ + private final AncientMode ancientMode; /** stores the minimum judge ancient identifier for all decided and non-expired rounds */ private final SequentialRingBuffer minimumJudgeStorage; /** a derivative of the only roster currently in use, until roster changes are implemented */ @@ -33,12 +36,21 @@ public class ConsensusRounds { private long maxRoundCreated = ConsensusConstants.ROUND_UNDEFINED; /** The round we are currently voting on */ private final RoundElections roundElections = new RoundElections(); - /** the minimum generation of all the judges that are not ancient */ - private long minGenNonAncient = EventConstants.FIRST_GENERATION; + /** the current threshold below which all events are ancient */ + private long ancientThreshold = EventConstants.ANCIENT_THRESHOLD_UNDEFINED; + /** + * the minimum generation of all the judges in the latest decided round. events with a lower generation than this do + * not affect any consensus calculations + */ + private long consensusRelevantGeneration = EventConstants.GENERATION_UNDEFINED; /** Constructs an empty object */ - public ConsensusRounds(@NonNull final ConsensusConfig config, @NonNull final Roster roster) { + public ConsensusRounds( + @NonNull final ConsensusConfig config, + @NonNull final AncientMode ancientMode, + @NonNull final Roster roster) { this.config = Objects.requireNonNull(config); + this.ancientMode = Objects.requireNonNull(ancientMode); this.minimumJudgeStorage = new SequentialRingBuffer<>(ConsensusConstants.ROUND_FIRST, config.roundsExpired() * 2); this.rosterEntryMap = RosterUtils.toMap(Objects.requireNonNull(roster)); @@ -50,7 +62,8 @@ public void reset() { minimumJudgeStorage.reset(ConsensusConstants.ROUND_FIRST); maxRoundCreated = ConsensusConstants.ROUND_UNDEFINED; roundElections.reset(); - updateMinGenNonAncient(); + updateAncientThreshold(); + consensusRelevantGeneration = EventConstants.GENERATION_UNDEFINED; } /** @@ -105,9 +118,16 @@ public void recalculating() { * @return true if its older */ public boolean isOlderThanDecidedRoundGeneration(@NonNull final EventImpl event) { - return isAnyRoundDecided() // if no round has been decided, it can't be older - && minimumJudgeStorage.get(getLastRoundDecided()).minimumJudgeAncientThreshold() - > event.getGeneration(); + return consensusRelevantGeneration > event.getGeneration(); + } + + /** + * Setter for the {@link #consensusRelevantGeneration} field. This is used when loading consensus from a snapshot. + * + * @param consensusRelevantGeneration the value to set + */ + public void setConsensusRelevantGeneration(final long consensusRelevantGeneration) { + this.consensusRelevantGeneration = consensusRelevantGeneration; } /** @@ -121,11 +141,12 @@ private boolean isAnyRoundDecided() { * Notifies the instance that the current elections have been decided. This will start the next election. */ public void currentElectionDecided() { - minimumJudgeStorage.add(roundElections.getRound(), roundElections.createMinimumJudgeInfo()); + minimumJudgeStorage.add(roundElections.getRound(), roundElections.createMinimumJudgeInfo(ancientMode)); + consensusRelevantGeneration = roundElections.getMinGeneration(); roundElections.startNextElection(); // Delete the oldest rounds with round number which is expired minimumJudgeStorage.removeOlderThan(getFameDecidedBelow() - config.roundsExpired()); - updateMinGenNonAncient(); + updateAncientThreshold(); } /** @@ -179,7 +200,7 @@ public void loadFromMinimumJudge(@NonNull final List minimumJu minimumJudgeStorage.add(minimumJudgeInfo.round(), minimumJudgeInfo); } roundElections.setRound(minimumJudgeStorage.getLatest().round() + 1); - updateMinGenNonAncient(); + updateAncientThreshold(); } /** @@ -216,62 +237,38 @@ public long getMaxRound() { } /** - * @return The minimum judge generation number from the oldest non-expired round, if we have expired any rounds. - * Else this returns {@link EventConstants#FIRST_GENERATION}. + * Similar to {@link #getAncientThreshold()} but for expired rounds. + * + * @return the threshold for expired rounds */ - public long getMinRoundGeneration() { + public long getExpiredThreshold() { final MinimumJudgeInfo info = minimumJudgeStorage.get(minimumJudgeStorage.minIndex()); - return info == null ? EventConstants.FIRST_GENERATION : info.minimumJudgeAncientThreshold(); + return info == null ? EventConstants.ANCIENT_THRESHOLD_UNDEFINED : info.minimumJudgeAncientThreshold(); } /** - * Update the oldest non-ancient round generation - * - *

Executed only on consensus thread. + * Update the current ancient threshold based on the latest round decided. */ - private void updateMinGenNonAncient() { - if (getFameDecidedBelow() == ConsensusConstants.ROUND_FIRST) { + private void updateAncientThreshold() { + if (!isAnyRoundDecided()) { // if no round has been decided, no events are ancient yet - minGenNonAncient = EventConstants.FIRST_GENERATION; + ancientThreshold = EventConstants.ANCIENT_THRESHOLD_UNDEFINED; return; } final long nonAncientRound = RoundCalculationUtils.getOldestNonAncientRound(config.roundsNonAncient(), getLastRoundDecided()); final MinimumJudgeInfo info = minimumJudgeStorage.get(nonAncientRound); - minGenNonAncient = info.minimumJudgeAncientThreshold(); + ancientThreshold = info.minimumJudgeAncientThreshold(); } /** - * Return the minimum generation of all the famous witnesses that are not in ancient rounds. + * Returns the threshold of all the judges that are not in ancient rounds. This is either a generation value or a + * birth round value, depending on the ancient mode configured. If no judges are ancient, returns + * {@link EventConstants#FIRST_GENERATION} or {@link ConsensusConstants#ROUND_FIRST} depending on the ancient mode. * - *

Define gen(R) to be the minimum generation of all the events that were famous witnesses in - * round R. - * - *

If round R is the most recent round for which we have decided the fame of all the - * witnesses, then any event with a generation less than gen(R - {@code Settings.state.roundsExpired}) is called an - * “expired” event. And any non-expired event with a generation less than gen(R - - * {@code Settings.state.roundsNonAncient} + 1) is an “ancient” event. If the event failed to achieve consensus - * before becoming ancient, then it is “stale”. So every non-expired event with a generation before gen(R - - * {@code Settings.state.roundsNonAncient} + 1) is either stale or consensus, not both. - * - *

Expired events can be removed from memory unless they are needed for an old signed state - * that is still being used for something (such as still in the process of being written to disk). - * - * @return The minimum generation of all the judges that are not ancient. If no judges are ancient, returns - * {@link EventConstants#FIRST_GENERATION}. - */ - public long getMinGenerationNonAncient() { - return minGenNonAncient; - } - - /** - * Checks if the supplied event is ancient or not. An event is ancient if its generation is smaller than the round - * generation of the oldest non-ancient round. - * - * @param event the event to check - * @return true if the event is ancient, false otherwise + * @return the threshold */ - public boolean isAncient(@NonNull final EventImpl event) { - return event.getGeneration() < getMinGenerationNonAncient(); + public long getAncientThreshold() { + return ancientThreshold; } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSnapshot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSnapshot.java index 702528cdc5dd..51e1d174f149 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSnapshot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/ConsensusSnapshot.java @@ -113,9 +113,9 @@ public long nextConsensusNumber() { * @param roundsNonAncient the number of non-ancient rounds * @return minimum non-ancient generation */ - public long getMinimumGenerationNonAncient(final int roundsNonAncient) { - return RoundCalculationUtils.getMinGenNonAncient( - roundsNonAncient, round, this::getMinimumJudgeAncientThreshold); + public long getAncientThreshold(final int roundsNonAncient) { + final long oldestNonAncientRound = RoundCalculationUtils.getOldestNonAncientRound(roundsNonAncient, round); + return getMinimumJudgeAncientThreshold(oldestNonAncientRound); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java index 834391e447f0..a132b858cd4f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java @@ -2,7 +2,6 @@ package com.swirlds.platform.consensus; import com.swirlds.platform.system.events.EventConstants; -import java.util.function.LongUnaryOperator; /** * Utilities for calculating round numbers @@ -25,24 +24,4 @@ public static long getOldestNonAncientRound(final int roundsNonAncient, final lo // if no rounds are ancient yet, then the oldest non-ancient round is the first round ever return Math.max(lastRoundDecided - roundsNonAncient + 1, EventConstants.MINIMUM_ROUND_CREATED); } - - /** - * Returns the minimum generation below which all events are ancient - * - * @param roundsNonAncient - * the number of non-ancient rounds - * @param lastRoundDecided - * the last round that has fame decided - * @param roundGenerationProvider - * returns a round generation number for a given round number - * @return minimum non-ancient generation - */ - public static long getMinGenNonAncient( - final int roundsNonAncient, final long lastRoundDecided, final LongUnaryOperator roundGenerationProvider) { - // if a round generation is not defined for the oldest round, it will be EventConstants.GENERATION_UNDEFINED, - // which is -1. in this case we will return FIRST_GENERATION, which is 0 - return Math.max( - roundGenerationProvider.applyAsLong(getOldestNonAncientRound(roundsNonAncient, lastRoundDecided)), - EventConstants.FIRST_GENERATION); - } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java index df7697dce157..714644335915 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundElections.java @@ -6,6 +6,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.IntReference; import com.swirlds.platform.Utilities; +import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.state.MinimumJudgeInfo; import com.swirlds.platform.system.events.EventConstants; @@ -37,6 +38,8 @@ public class RoundElections { private final List elections = new ArrayList<>(); /** the minimum generation of all the judges, this is only set once the judges are found */ private long minGeneration = EventConstants.GENERATION_UNDEFINED; + /** the minimum birth round of all the judges, this is only set once the judges are found */ + private long minBirthRound = EventConstants.BIRTH_ROUND_UNDEFINED; /** * @return the round number of witnesses we are voting on @@ -106,11 +109,22 @@ public long getMinGeneration() { return minGeneration; } + /** + * @return the minimum birth round of all the judges(unique famous witnesses) in this round + */ + private long getMinBirthRound() { + if (minBirthRound == EventConstants.BIRTH_ROUND_UNDEFINED) { + throw new IllegalStateException("Cannot provide the minimum birth round until all judges are found"); + } + return minBirthRound; + } + /** * @return create a {@link MinimumJudgeInfo} instance for this round */ - public @NonNull MinimumJudgeInfo createMinimumJudgeInfo() { - return new MinimumJudgeInfo(round, getMinGeneration()); + public @NonNull MinimumJudgeInfo createMinimumJudgeInfo(final AncientMode ancientMode) { + return new MinimumJudgeInfo( + round, ancientMode == AncientMode.GENERATION_THRESHOLD ? getMinGeneration() : getMinBirthRound()); } /** @@ -133,12 +147,17 @@ public long getMinGeneration() { election.getWitness().getCreatorId(), election.getWitness(), RoundElections::uniqueFamous); } final List allJudges = new ArrayList<>(uniqueFamous.values()); + if (allJudges.isEmpty()) { + throw new IllegalStateException("No judges found in round " + round); + } allJudges.sort(Comparator.comparingLong(e -> e.getCreatorId().id())); - minGeneration = allJudges.stream() - .mapToLong(EventImpl::getGeneration) - .min() - .orElse(EventConstants.GENERATION_UNDEFINED); - allJudges.forEach(EventImpl::setJudgeTrue); + minGeneration = Long.MAX_VALUE; + minBirthRound = Long.MAX_VALUE; + for (final EventImpl judge : allJudges) { + minGeneration = Math.min(minGeneration, judge.getGeneration()); + minBirthRound = Math.min(minBirthRound, judge.getBirthRound()); + judge.setJudgeTrue(); + } return allJudges; } @@ -170,6 +189,7 @@ public void startNextElection() { numUnknownFame.set(0); elections.clear(); minGeneration = EventConstants.GENERATION_UNDEFINED; + minBirthRound = EventConstants.BIRTH_ROUND_UNDEFINED; } /** Reset this instance to its initial state */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/SyntheticSnapshot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/SyntheticSnapshot.java index e0b2f48db366..f39b6eab8495 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/SyntheticSnapshot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/SyntheticSnapshot.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.consensus; +import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.state.MinimumJudgeInfo; import com.swirlds.platform.system.events.EventConstants; @@ -13,14 +14,6 @@ * Utility class for generating "synthetic" snapshots */ public final class SyntheticSnapshot { - /** genesis snapshot, when loaded by consensus, it will start from genesis */ - public static final ConsensusSnapshot GENESIS_SNAPSHOT = new ConsensusSnapshot( - ConsensusConstants.ROUND_FIRST, - List.of(), - List.of(new MinimumJudgeInfo(ConsensusConstants.ROUND_FIRST, EventConstants.FIRST_GENERATION)), - ConsensusConstants.FIRST_CONSENSUS_NUMBER, - Instant.EPOCH); - /** Utility class, should not be instantiated */ private SyntheticSnapshot() {} @@ -36,6 +29,7 @@ private SyntheticSnapshot() {} * @param lastConsensusOrder the last consensus order of all events that have reached consensus * @param roundTimestamp the timestamp of the round * @param config the consensus configuration + * @param ancientMode the ancient mode * @param judge the judge event * @return the synthetic snapshot */ @@ -44,10 +38,11 @@ private SyntheticSnapshot() {} final long lastConsensusOrder, @NonNull final Instant roundTimestamp, @NonNull final ConsensusConfig config, + @NonNull final AncientMode ancientMode, @NonNull final PlatformEvent judge) { final List minimumJudgeInfos = LongStream.range( RoundCalculationUtils.getOldestNonAncientRound(config.roundsNonAncient(), round), round + 1) - .mapToObj(r -> new MinimumJudgeInfo(r, judge.getGeneration())) + .mapToObj(r -> new MinimumJudgeInfo(r, judge.getAncientIndicator(ancientMode))) .toList(); return new ConsensusSnapshot( round, @@ -58,9 +53,22 @@ private SyntheticSnapshot() {} } /** - * @return the genesis snapshot + * Create a genesis snapshot. This snapshot is not the result of consensus but is instead generated to be used as a + * starting point for consensus. + * + * @param ancientMode the ancient mode + * @return the genesis snapshot, when loaded by consensus, it will start from genesis */ - public static @NonNull ConsensusSnapshot getGenesisSnapshot() { - return GENESIS_SNAPSHOT; + public static @NonNull ConsensusSnapshot getGenesisSnapshot(@NonNull final AncientMode ancientMode) { + return new ConsensusSnapshot( + ConsensusConstants.ROUND_FIRST, + List.of(), + List.of(new MinimumJudgeInfo( + ConsensusConstants.ROUND_FIRST, + ancientMode == AncientMode.GENERATION_THRESHOLD + ? EventConstants.FIRST_GENERATION + : ConsensusConstants.ROUND_FIRST)), + ConsensusConstants.FIRST_CONSENSUS_NUMBER, + Instant.EPOCH); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/PlatformEvent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/PlatformEvent.java index c18aac45bbb6..fdab717765e2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/PlatformEvent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/PlatformEvent.java @@ -131,7 +131,7 @@ private PlatformEvent(@NonNull final GossipEvent gossipEvent, @NonNull final Eve * * @return a copy of this event */ - public PlatformEvent copyGossipedData() { + public @NonNull PlatformEvent copyGossipedData() { final PlatformEvent platformEvent = new PlatformEvent(gossipEvent); platformEvent.setHash(getHash()); return platformEvent; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java index 6e98da477094..37eacd417af1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.gui; -import static com.swirlds.platform.event.AncientMode.GENERATION_THRESHOLD; import static com.swirlds.platform.system.events.EventConstants.FIRST_GENERATION; import com.swirlds.common.context.PlatformContext; @@ -11,6 +10,7 @@ import com.swirlds.platform.consensus.ConsensusConfig; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.event.PlatformEvent; +import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.NoOpConsensusMetrics; @@ -37,7 +37,7 @@ public class GuiEventStorage { private ConsensusRound lastConsensusRound; /** - * Constructor + * Creates an empty instance * * @param configuration this node's configuration * @param addressBook the network's address book @@ -49,8 +49,27 @@ public GuiEventStorage(@NonNull final Configuration configuration, @NonNull fina this.consensus = new ConsensusImpl( platformContext, new NoOpConsensusMetrics(), RosterRetriever.buildRoster(addressBook)); - // Future work: birth round compatibility for GUI - this.linker = new SimpleLinker(GENERATION_THRESHOLD); + this.linker = + new SimpleLinker(configuration.getConfigData(EventConfig.class).getAncientMode()); + } + + /** + * Creates an instance with the given consensus, linker, and configuration. + * @param consensus the consensus object + * @param linker the linker object + * @param configuration the configuration object + */ + public GuiEventStorage( + @NonNull final Consensus consensus, + @NonNull final SimpleLinker linker, + @NonNull final Configuration configuration) { + this.consensus = consensus; + this.linker = linker; + this.configuration = configuration; + maxGeneration = linker.getNonAncientEvents().stream() + .mapToLong(EventImpl::getGeneration) + .max() + .orElse(FIRST_GENERATION); } /** @@ -94,7 +113,7 @@ public synchronized void handlePreconsensusEvent(@NonNull final PlatformEvent ev public synchronized void handleSnapshotOverride(@NonNull final ConsensusSnapshot snapshot) { consensus.loadSnapshot(snapshot); linker.clear(); - linker.setNonAncientThreshold(snapshot.getMinimumGenerationNonAncient( + linker.setNonAncientThreshold(snapshot.getAncientThreshold( configuration.getConfigData(ConsensusConfig.class).roundsNonAncient())); lastConsensusRound = null; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/SimpleLinker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/SimpleLinker.java index ccf35cbc9a22..cbbf24706e24 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/SimpleLinker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/SimpleLinker.java @@ -13,6 +13,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -44,6 +45,7 @@ public class SimpleLinker { */ private final Map parentHashMap = new HashMap<>(INITIAL_CAPACITY); + private final AncientMode ancientMode; private long nonAncientThreshold = 0; /** @@ -52,12 +54,13 @@ public class SimpleLinker { * @param ancientMode the ancient mode */ public SimpleLinker(@NonNull final AncientMode ancientMode) { - if (ancientMode == AncientMode.BIRTH_ROUND_THRESHOLD) { - throw new UnsupportedOperationException("not yet supported"); - } else { - this.parentDescriptorMap = new StandardSequenceMap<>( - 0, INITIAL_CAPACITY, true, ed -> ed.eventDescriptor().generation()); - } + this.ancientMode = ancientMode; + this.parentDescriptorMap = new StandardSequenceMap<>( + 0, + INITIAL_CAPACITY, + true, + ed -> ancientMode.selectIndicator( + ed.eventDescriptor().generation(), ed.eventDescriptor().birthRound())); } /** @@ -125,7 +128,7 @@ private EventImpl getParentToLink( */ @Nullable public EventImpl linkEvent(@NonNull final PlatformEvent event) { - if (event.getAncientIndicator(AncientMode.GENERATION_THRESHOLD) < nonAncientThreshold) { + if (event.getAncientIndicator(ancientMode) < nonAncientThreshold) { // This event is ancient, so we don't need to link it. return null; } @@ -170,6 +173,18 @@ public List getNonAncientEvents() { return parentHashMap.values().stream().toList(); } + /** + * Get all non-ancient events tracked by this linker sorted in topological order. + * + * @return all non-ancient events + */ + @NonNull + public List getSortedNonAncientEvents() { + return parentHashMap.values().stream() + .sorted(Comparator.comparing(EventImpl::getGeneration)) + .toList(); + } + /** * Clear the internal state of this linker. */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/HashgraphPictureOptions.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/HashgraphPictureOptions.java index c3e1dec707be..714a57f9f788 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/HashgraphPictureOptions.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/HashgraphPictureOptions.java @@ -40,6 +40,11 @@ public interface HashgraphPictureOptions { */ boolean writeGeneration(); + /** + * @return should the birth round be written for every event + */ + boolean writeBirthRound(); + /** * @return should simple colors be used in the picture */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphGuiControls.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphGuiControls.java index e5bf2f6af3c7..5e41aa1f20be 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphGuiControls.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphGuiControls.java @@ -43,6 +43,8 @@ public class HashgraphGuiControls implements HashgraphPictureOptions { private final Checkbox labelConsTimestampCheckbox; /** the generation number for the event */ private final Checkbox labelGenerationCheckbox; + /** the birth round number for the event */ + private final Checkbox labelBirthroundCheckbox; /** check to display the latest events available */ private final Checkbox displayLatestEvents; @@ -62,6 +64,7 @@ public HashgraphGuiControls(final ItemListener freezeListener) { labelConsOrderCheckbox = new Checkbox("Labels: Order (consensus)"); labelConsTimestampCheckbox = new Checkbox("Labels: Timestamp (consensus)"); labelGenerationCheckbox = new Checkbox("Labels: Generation"); + labelBirthroundCheckbox = new Checkbox("Labels: Birth round"); displayLatestEvents = new Checkbox("Display latest events"); displayLatestEvents.setState(true); @@ -97,6 +100,7 @@ public HashgraphGuiControls(final ItemListener freezeListener) { labelConsOrderCheckbox, labelConsTimestampCheckbox, labelGenerationCheckbox, + labelBirthroundCheckbox, displayLatestEvents }; } @@ -229,6 +233,11 @@ public boolean writeGeneration() { return labelGenerationCheckbox.getState(); } + @Override + public boolean writeBirthRound() { + return labelBirthroundCheckbox.getState(); + } + @Override public boolean simpleColors() { return simpleColorsCheckbox.getState(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphPicture.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphPicture.java index d2b7ba8039fa..cf2bbac5293a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphPicture.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/hashgraph/internal/HashgraphPicture.java @@ -210,6 +210,10 @@ private void drawEventCircle( if (options.writeGeneration()) { s += " " + event.getGeneration(); } + + if (options.writeBirthRound()) { + s += " " + event.getBirthRound(); + } if (!s.isEmpty()) { final Rectangle2D rect = fm.getStringBounds(s, g); final int x = (int) (pictureMetadata.xpos(e2, event) - rect.getWidth() / 2. - fa / 4.); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java index 5fa50de4c046..722cfb68beb8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java @@ -6,8 +6,10 @@ import com.swirlds.common.utility.Clearable; import com.swirlds.platform.consensus.CandidateWitness; import com.swirlds.platform.consensus.ConsensusConstants; +import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.EventCounter; import com.swirlds.platform.event.PlatformEvent; +import com.swirlds.platform.system.events.EventDescriptorWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -514,6 +516,20 @@ public long getBirthRound() { return baseEvent.getBirthRound(); } + /** + * Get the age value of this event based on the ancient mode. The age value is either the generation or the birth + * round of this event. + * + * @param ancientMode the ancient mode + * @return the age value of this event + */ + public long getAgeValue(@NonNull final AncientMode ancientMode) { + return switch (ancientMode) { + case GENERATION_THRESHOLD -> getGeneration(); + case BIRTH_ROUND_THRESHOLD -> getBirthRound(); + }; + } + /** * Same as {@link PlatformEvent#getCreatorId()} */ @@ -548,6 +564,20 @@ public int hashCode() { @Override public String toString() { - return baseEvent.toString(); + final StringBuilder sb = new StringBuilder(); + baseEvent.getDescriptor().shortString(sb); + final List allParents = baseEvent.getAllParents(); + for (final EventDescriptorWrapper parent : allParents) { + parent.shortString(sb); + } + return sb.toString(); + } + + /** + * Create a short string representation of this event without any parent information. + * @return a short string + */ + public String shortString() { + return baseEvent.getDescriptor().shortString(); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index 7c2cd4fc94d2..b53fab42c770 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -383,7 +383,15 @@ private static ReservedSignedState handleNextRound( getHashEventsCons(platformStateFacade.legacyRunningEventHashOf(newState), round)); v.setConsensusTimestamp(currentRoundTimestamp); v.setSnapshot(SyntheticSnapshot.generateSyntheticSnapshot( - round.getRoundNum(), lastEvent.getConsensusOrder(), currentRoundTimestamp, config, lastEvent)); + round.getRoundNum(), + lastEvent.getConsensusOrder(), + currentRoundTimestamp, + config, + platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode(), + lastEvent)); v.setCreationSoftwareVersion(platformStateFacade.creationSoftwareVersionOf(previousState.getState())); }); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java index 7f179ce47790..08eef0d68fa5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java @@ -21,6 +21,8 @@ private EventConstants() {} public static final long MINIMUM_ROUND_CREATED = 1; /** the round number to represent that the birth round is undefined */ public static final long BIRTH_ROUND_UNDEFINED = -1; + /** represent either a birth round or a generation which is undefined */ + public static final long ANCIENT_THRESHOLD_UNDEFINED = -1; /** the minimum generation value an event can have. */ public static final long FIRST_GENERATION = 0; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventDescriptorWrapper.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventDescriptorWrapper.java index cdc23a9780e6..2ccf68fb1cef 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventDescriptorWrapper.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventDescriptorWrapper.java @@ -6,6 +6,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.platform.event.AncientMode; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; /** * A wrapper class for {@link EventDescriptor} that includes the hash of the event descriptor. @@ -30,4 +31,35 @@ public long getAncientIndicator(@NonNull final AncientMode ancientMode) { case BIRTH_ROUND_THRESHOLD -> eventDescriptor.birthRound(); }; } + + /** + * Create a short string representation of this event descriptor. + * @return a short string + */ + public @NonNull String shortString() { + return shortString(new StringBuilder()).toString(); + } + + /** + * Append a short string representation of this event descriptor to the given {@link StringBuilder}. + * @param sb the {@link StringBuilder} to append to + * @return the given {@link StringBuilder} + */ + public @NonNull StringBuilder shortString(@NonNull final StringBuilder sb) { + Objects.requireNonNull(sb) + .append('(') + .append("CR:") + .append(creator().id()) + .append(" ") + .append("H:") + .append(hash().toHex(6)) + .append(" ") + .append("G:") + .append(eventDescriptor().generation()) + .append(" ") + .append("BR:") + .append(eventDescriptor().birthRound()) + .append(')'); + return sb; + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/UnsignedEvent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/UnsignedEvent.java index 50519d68effb..463423282366 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/UnsignedEvent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/UnsignedEvent.java @@ -54,7 +54,6 @@ public UnsignedEvent( final long birthRound, @NonNull final Instant timeCreated, @NonNull final List transactions) { - Objects.requireNonNull(transactions, "The transactions must not be null"); this.transactions = Objects.requireNonNull(transactions, "transactions must not be null"); this.metadata = new EventMetadata(creatorId, selfParent, otherParents, timeCreated, transactions); this.eventCore = new EventCore( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java index cc69ae0a742a..05004ce611b2 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java @@ -2,9 +2,6 @@ package com.swirlds.platform.consensus; import com.swirlds.platform.system.events.EventConstants; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.LongStream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -21,23 +18,4 @@ void getOldestNonAncientRound() { RoundCalculationUtils.getOldestNonAncientRound(10, 5), "if no rounds are ancient yet, then the oldest one is the first round"); } - - @Test - void getMinGenNonAncient() { - // generation is equal to round*10 - final Map map = - LongStream.range(1, 50).collect(HashMap::new, (m, l) -> m.put(l, l * 10), HashMap::putAll); - Assertions.assertEquals( - 60, - RoundCalculationUtils.getMinGenNonAncient(5, 10, map::get), - "if the oldest non-ancient round is 6, then the generation should 60"); - Assertions.assertEquals( - 10, - RoundCalculationUtils.getMinGenNonAncient(10, 5, map::get), - "if no rounds are ancient yet, then the minGenNonAncient is the first round generation"); - Assertions.assertEquals( - EventConstants.FIRST_GENERATION, - RoundCalculationUtils.getMinGenNonAncient(10, 5, l -> EventConstants.GENERATION_UNDEFINED), - "if no round generation is not defined yet, then the minGenNonAncient is the first generation"); - } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java index 3a272c0b3cfe..6236ca77ad60 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/DefaultTransactionHandlerTests.java @@ -85,7 +85,7 @@ private ConsensusRound newConsensusRound(final boolean pcesRound) { events, keystone, EventWindow.getGenesisEventWindow(AncientMode.GENERATION_THRESHOLD), - SyntheticSnapshot.GENESIS_SNAPSHOT, + SyntheticSnapshot.getGenesisSnapshot(AncientMode.GENERATION_THRESHOLD), pcesRound, random.nextInstant()); diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/GraphGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/GraphGenerator.java index 2c9331b64d66..244ccaf66d5b 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/GraphGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/GraphGenerator.java @@ -8,7 +8,6 @@ import com.swirlds.platform.test.fixtures.event.source.EventSource; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -52,14 +51,6 @@ default GraphGenerator copy() { */ GraphGenerator cleanCopy(); - /** - * Get a clean copy but with a different seed. - * - * @param seed - * The new seed to use. - */ - GraphGenerator cleanCopy(long seed); - /** * Reset this generator to its original state. Does not undo settings changes, just the events that have been * emitted. @@ -149,9 +140,11 @@ default List generateEvents(final int numberOfEvents) { void setOtherParentAffinity(final DynamicValue>> affinityMatrix); /** - * Sets the timestamp of the last emitted event - * - * @param previousTimestamp the timestamp to set + * Remove a node from the generator. This will remove it from the address book and it will remove its event source, + * so that this node will not generate any more events. + * NOTE: This method is created specifically for a single node removal. For more complex address book changes this + * functionality should be expanded. + * @param nodeId the node to remove */ - void setPreviousTimestamp(final Instant previousTimestamp); + void removeNode(@NonNull final NodeId nodeId); } diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java index fd381fe6ef07..b1bebc8abe40 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/StandardGraphGenerator.java @@ -8,10 +8,16 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.ConsensusImpl; +import com.swirlds.platform.consensus.ConsensusConfig; +import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.event.hashing.DefaultEventHasher; -import com.swirlds.platform.event.linking.ConsensusLinker; -import com.swirlds.platform.event.linking.InOrderLinker; +import com.swirlds.platform.eventhandling.EventConfig; +import com.swirlds.platform.gui.GuiEventStorage; +import com.swirlds.platform.gui.SimpleLinker; +import com.swirlds.platform.gui.hashgraph.HashgraphGuiSource; +import com.swirlds.platform.gui.hashgraph.internal.StandardGuiSource; +import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.NoOpConsensusMetrics; import com.swirlds.platform.roster.RosterRetriever; @@ -78,15 +84,18 @@ public class StandardGraphGenerator extends AbstractGraphGenerator { */ private ConsensusImpl consensus; + /** The latest snapshot to be produced by {@link #consensus} */ + private ConsensusSnapshot consensusSnapshot; + /** * The platform context containing configuration for the internal consensus. */ - private PlatformContext platformContext; + private final PlatformContext platformContext; /** * The linker for events to use with the internal consensus. */ - private InOrderLinker inOrderLinker; + private SimpleLinker linker; /** * Construct a new StandardEventGenerator. @@ -110,7 +119,9 @@ public StandardGraphGenerator( * @param eventSources One or more event sources. */ public StandardGraphGenerator( - @NonNull PlatformContext platformContext, final long seed, @NonNull final List eventSources) { + @NonNull final PlatformContext platformContext, + final long seed, + @NonNull final List eventSources) { super(seed); this.platformContext = Objects.requireNonNull(platformContext); this.sources = Objects.requireNonNull(eventSources); @@ -179,7 +190,10 @@ private StandardGraphGenerator(final StandardGraphGenerator that, final long see private void initializeInternalConsensus() { consensus = new ConsensusImpl( platformContext, new NoOpConsensusMetrics(), RosterRetriever.buildRoster(addressBook)); - inOrderLinker = new ConsensusLinker(platformContext, NodeId.of(0)); + linker = new SimpleLinker(platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode()); } /** @@ -269,40 +283,6 @@ private void buildDefaultOtherParentAffinityMatrix() { affinityMatrix = new DynamicValueGenerator<>(staticDynamicValue(matrix)); } - /** - * Get the average difference in the timestamp between two adjacent events (in seconds). - */ - public double getEventPeriodMean() { - return eventPeriodMean; - } - - /** - * Set the average difference in the timestamp between two adjacent events (in seconds). - * - * @return this - */ - public StandardGraphGenerator setEventPeriodMean(final double eventPeriodMean) { - this.eventPeriodMean = eventPeriodMean; - return this; - } - - /** - * Get the standard deviation of the difference of the timestamp between two adjacent events (in seconds). - */ - public double getEventPeriodStandardDeviation() { - return eventPeriodStandardDeviation; - } - - /** - * Set the standard deviation of the difference of the timestamp between two adjacent events (in seconds). - * - * @return this - */ - public StandardGraphGenerator setEventPeriodStandardDeviation(final double eventPeriodStandardDeviation) { - this.eventPeriodStandardDeviation = eventPeriodStandardDeviation; - return this; - } - /** * Set the probability, as a fraction of 1.0, that an event has the same timestamp as the proceeding event. If the * proceeding event has the same self parent then this is ignored and the events are not made to be simultaneous. @@ -314,12 +294,9 @@ public double getSimultaneousEventFraction() { /** * Get the probability, as a fraction of 1.0, that an event has the same timestamp as the proceeding event. If the * proceeding event has the same self parent then this is ignored and the events are not made to be simultaneous. - * - * @return this */ - public StandardGraphGenerator setSimultaneousEventFraction(final double simultaneousEventFraction) { + public void setSimultaneousEventFraction(final double simultaneousEventFraction) { this.simultaneousEventFraction = simultaneousEventFraction; - return this; } /** @@ -342,25 +319,16 @@ public int getNumberOfSources() { * {@inheritDoc} */ @Override - public EventSource getSource(final NodeId nodeID) { + public EventSource getSource(@NonNull final NodeId nodeID) { final int nodeIndex = addressBook.getIndexOfNodeId(nodeID); return sources.get(nodeIndex); } - /** - * Get the event source for a particular node index. - * - * @return the event source - */ - public EventSource getSourceByIndex(final int nodeIndex) { - return sources.get(nodeIndex); - } - /** * {@inheritDoc} */ @Override - public AddressBook getAddressBook() { + public @NonNull AddressBook getAddressBook() { return addressBook; } @@ -381,14 +349,6 @@ private List getSourceWeights(final long eventIndex) { return sourceWeights; } - /** - * {@inheritDoc} - */ - @Override - public StandardGraphGenerator cleanCopy(final long newSeed) { - return new StandardGraphGenerator(this, newSeed); - } - /** * {@inheritDoc} */ @@ -475,21 +435,57 @@ public EventImpl buildNextEvent(final long eventIndex) { getNextTimestamp(source, otherParentSource.getNodeId()), birthRound); - // The event given to the internal consensus needs its own EventImpl & PlatformEvent for metadata to be kept - // separate from the event that is returned to the caller. This InOrderLinker wraps the event in an EventImpl - // and links it. The event must be hashed and have a descriptor built for its use in the InOrderLinker. - // This may leak memory, but is fine in the current testing framework. - // When the test ends any memory used will be released. new DefaultEventHasher().hashEvent(next.getBaseEvent()); - final PlatformEvent tmp = next.getBaseEvent().copyGossipedData(); - tmp.setHash(next.getBaseEvent().getHash()); - consensus.addEvent(inOrderLinker.linkEvent(tmp)); - + updateConsensus(next); return next; } + private void updateConsensus(@NonNull final EventImpl e) { + // The event given to the internal consensus needs its own EventImpl & PlatformEvent for metadata to be kept + // separate from the event that is returned to the caller. This SimpleLinker wraps the event in an EventImpl + // and links it. The event must be hashed and have a descriptor built for its use in the SimpleLinker. + final PlatformEvent copy = e.getBaseEvent().copyGossipedData(); + final EventImpl linkedEvent = linker.linkEvent(copy); + if (linkedEvent == null) { + return; + } + + final List consensusRounds = consensus.addEvent(linkedEvent); + if (consensusRounds.isEmpty()) { + return; + } + // if we reach consensus, save the snapshot for future use + consensusSnapshot = consensusRounds.getLast().getSnapshot(); + linker.setNonAncientThreshold(consensusRounds.getLast().getEventWindow().getAncientThreshold()); + } + @Override - public void setPreviousTimestamp(final Instant previousTimestamp) { - this.previousTimestamp = previousTimestamp; + public void removeNode(@NonNull final NodeId nodeId) { + // currently, we only support removing a node at restart, so this process mimics what happens at restart + + // remove the node from the address book and the sources + final int nodeIndex = addressBook.getIndexOfNodeId(nodeId); + sources.remove(nodeIndex); + addressBook = addressBook.remove(nodeId); + buildDefaultOtherParentAffinityMatrix(); + // save all non-ancient events + final List nonAncientEvents = linker.getSortedNonAncientEvents(); + // reinitialize the internal consensus with the last snapshot + initializeInternalConsensus(); + consensus.loadSnapshot(consensusSnapshot); + linker.setNonAncientThreshold(consensusSnapshot.getAncientThreshold(platformContext + .getConfiguration() + .getConfigData(ConsensusConfig.class) + .roundsNonAncient())); + // re-add all non-ancient events + for (final EventImpl event : nonAncientEvents) { + updateConsensus(event); + } + } + + @SuppressWarnings("unused") // useful for debugging + public HashgraphGuiSource createGuiSource() { + return new StandardGuiSource( + addressBook, new GuiEventStorage(consensus, linker, platformContext.getConfiguration())); } } diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/source/AbstractEventSource.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/source/AbstractEventSource.java index f0ce9baa1fa7..00ff773f8d4a 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/source/AbstractEventSource.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/source/AbstractEventSource.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.test.fixtures.event.source; -import static com.swirlds.platform.system.events.EventConstants.FIRST_GENERATION; import static com.swirlds.platform.test.fixtures.event.EventUtils.integerPowerDistribution; import static com.swirlds.platform.test.fixtures.event.EventUtils.staticDynamicValue; @@ -214,10 +213,6 @@ public EventImpl generateEvent( final EventImpl otherParentEvent = otherParent == null ? null : otherParent.getRecentEvent(random, otherParentIndex); final EventImpl latestSelfEvent = getLatestEvent(random); - final long generation = Math.max( - otherParentEvent == null ? (FIRST_GENERATION - 1) : otherParentEvent.getGeneration(), - latestSelfEvent == null ? (FIRST_GENERATION - 1) : latestSelfEvent.getGeneration()) - + 1; event = RandomEventUtils.randomEventWithTimestamp( random, diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java deleted file mode 100644 index 907f985a3131..000000000000 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package com.swirlds.platform.test.consensus; - -import com.swirlds.platform.event.PlatformEvent; -import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.system.address.Address; -import com.swirlds.platform.test.fixtures.event.generator.GraphGenerator; -import com.swirlds.platform.test.fixtures.event.source.EventSource; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Random; - -/** A class containing utilities for consensus tests. */ -public abstract class ConsensusUtils { - - public static void loadEventsIntoGenerator( - @NonNull final List events, - @NonNull final GraphGenerator generator, - @NonNull final Random random) { - Instant lastTimestamp = Instant.MIN; - for (final Address address : generator.getAddressBook()) { - final EventSource source = generator.getSource(address.getNodeId()); - final List eventsByCreator = events.stream() - .filter(e -> e.getCreatorId().id() == address.getNodeId().id()) - .toList(); - eventsByCreator.forEach(e -> { - final EventImpl eventImpl = new EventImpl(e, null, null); - source.setLatestEvent(random, eventImpl); - }); - final Instant creatorMax = eventsByCreator.stream() - .max(Comparator.comparingLong(PlatformEvent::getGeneration)) - .map(e -> e.getTimeCreated()) - .orElse(Instant.MIN); - lastTimestamp = Collections.max(Arrays.asList(lastTimestamp, creatorMax)); - } - generator.setPreviousTimestamp(lastTimestamp); - } -} diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java index 34ce10dd5395..3bffc3557f19 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java @@ -2,10 +2,7 @@ package com.swirlds.platform.test.consensus; import static com.swirlds.component.framework.wires.SolderType.INJECT; -import static com.swirlds.platform.consensus.SyntheticSnapshot.GENESIS_SNAPSHOT; -import static com.swirlds.platform.event.AncientMode.GENERATION_THRESHOLD; -import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; import com.swirlds.component.framework.component.ComponentWiring; @@ -21,15 +18,17 @@ import com.swirlds.platform.consensus.ConsensusConfig; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.EventWindow; +import com.swirlds.platform.consensus.SyntheticSnapshot; +import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.event.hashing.DefaultEventHasher; import com.swirlds.platform.event.hashing.EventHasher; import com.swirlds.platform.event.orphan.DefaultOrphanBuffer; import com.swirlds.platform.event.orphan.OrphanBuffer; +import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.NoOpIntakeEventCounter; import com.swirlds.platform.internal.ConsensusRound; -import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.roster.RosterRetriever; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.consensus.framework.ConsensusOutput; @@ -50,6 +49,7 @@ public class TestIntake { private final ComponentWiring> consensusEngineWiring; private final WiringModel model; private final int roundsNonAncient; + private final AncientMode ancientMode; /** * @param platformContext the platform context used to configure this intake. @@ -61,9 +61,12 @@ public TestIntake(@NonNull final PlatformContext platformContext, @NonNull final .getConfiguration() .getConfigData(ConsensusConfig.class) .roundsNonAncient(); + ancientMode = platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode(); - final Time time = Time.getCurrent(); - output = new ConsensusOutput(time); + output = new ConsensusOutput(ancientMode); model = WiringModelBuilder.create(platformContext).build(); @@ -122,15 +125,6 @@ public void addEvent(@NonNull final PlatformEvent event) { output.eventAdded(event); } - /** - * Same as {@link #addEvent(PlatformEvent)} but for a list of events - */ - public void addEvents(@NonNull final List events) { - for (final EventImpl event : events) { - addEvent(event.getBaseEvent()); - } - } - /** * @return a queue of all rounds that have reached consensus */ @@ -143,16 +137,11 @@ public void addEvents(@NonNull final List events) { } public void loadSnapshot(@NonNull final ConsensusSnapshot snapshot) { - - // FUTURE WORK: remove the fourth variable setting useBirthRound to false when we switch from comparing - // minGenNonAncient to comparing birthRound to minRoundNonAncient. Until then, it is always false in - // production. - final EventWindow eventWindow = new EventWindow( snapshot.round(), - snapshot.getMinimumGenerationNonAncient(roundsNonAncient), - snapshot.getMinimumGenerationNonAncient(roundsNonAncient), - GENERATION_THRESHOLD); + snapshot.getAncientThreshold(roundsNonAncient), + snapshot.getAncientThreshold(roundsNonAncient), + ancientMode); orphanBufferWiring.getInputWire(OrphanBuffer::setEventWindow).put(eventWindow); consensusEngineWiring @@ -165,13 +154,16 @@ public void loadSnapshot(@NonNull final ConsensusSnapshot snapshot) { } public void reset() { - loadSnapshot(GENESIS_SNAPSHOT); + loadSnapshot(SyntheticSnapshot.getGenesisSnapshot(ancientMode)); output.clear(); } public TaskScheduler directScheduler(final String name) { return model.schedulerBuilder(name) .withType(TaskSchedulerType.DIRECT) + .withUncaughtExceptionHandler((t, e) -> { + throw new RuntimeException("Uncaught exception in task " + t, e); + }) .build(); } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusOutput.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusOutput.java index 22f1c255882b..c7feaa1a39cd 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusOutput.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusOutput.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.test.consensus.framework; -import com.swirlds.base.time.Time; import com.swirlds.common.utility.Clearable; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.event.AncientMode; @@ -20,7 +19,7 @@ * Stores all output of consensus used in testing. This output can be used to validate consensus results. */ public class ConsensusOutput implements Clearable { - private final Time time; + private final AncientMode ancientMode; private final LinkedList consensusRounds; private final LinkedList addedEvents; private final LinkedList staleEvents; @@ -30,23 +29,28 @@ public class ConsensusOutput implements Clearable { private long latestRound; - private EventWindow eventWindow = EventWindow.getGenesisEventWindow(AncientMode.GENERATION_THRESHOLD); + private EventWindow eventWindow; /** * Creates a new instance. * - * @param time the time to use for marking events + * @param ancientMode the ancient mode */ - public ConsensusOutput(@NonNull final Time time) { - this.time = time; + public ConsensusOutput(@NonNull final AncientMode ancientMode) { + this.ancientMode = ancientMode; addedEvents = new LinkedList<>(); consensusRounds = new LinkedList<>(); staleEvents = new LinkedList<>(); - // FUTURE WORK: birth round compatibility - nonAncientEvents = new StandardSequenceSet<>(0, 1024, true, PlatformEvent::getGeneration); + nonAncientEvents = new StandardSequenceSet<>( + 0, 1024, true, e -> ancientMode.selectIndicator(e.getGeneration(), e.getBirthRound())); nonAncientConsensusEvents = new StandardSequenceSet<>( - 0, 1024, true, ed -> ed.eventDescriptor().generation()); + 0, + 1024, + true, + ed -> ancientMode.selectIndicator( + ed.eventDescriptor().generation(), ed.eventDescriptor().birthRound())); + eventWindow = EventWindow.getGenesisEventWindow(ancientMode); } public void eventAdded(@NonNull final PlatformEvent event) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java index 62f7ec881480..d05c5cb5c9b0 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java @@ -4,10 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.test.consensus.TestIntake; import com.swirlds.platform.test.event.emitter.EventEmitter; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.LinkedList; import java.util.Objects; import java.util.Random; @@ -59,6 +62,25 @@ public void restart() { intake.loadSnapshot(snapshot); } + /** + * Simulates removing a node from the network at restart + * @param nodeId the node to remove + */ + public void removeNode(@NonNull final NodeId nodeId) { + eventEmitter.getGraphGenerator().removeNode(nodeId); + final ConsensusSnapshot snapshot = Objects.requireNonNull( + getOutput().getConsensusRounds().peekLast()) + .getSnapshot(); + intake.loadSnapshot(snapshot); + // the above will clear all events from the linker and consensus, so we need to add all non-ancient events + // adding events will also add the events to the output, so we make a copy of the list and add them back + final LinkedList added = new LinkedList<>(getOutput().getAddedEvents()); + getOutput().getAddedEvents().clear(); + for (final PlatformEvent e : added) { + intake.addEvent(e.copyGossipedData()); + } + } + /** * Create a new {@link ConsensusTestNode} that will be created by simulating a reconnect with this context * diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java index 6e3dd4e8b268..1f337bb1be60 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java @@ -2,13 +2,13 @@ package com.swirlds.platform.test.consensus.framework; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; +import com.swirlds.common.platform.NodeId; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.test.consensus.framework.validation.ConsensusOutputValidation; import com.swirlds.platform.test.consensus.framework.validation.Validations; import com.swirlds.platform.test.fixtures.event.generator.GraphGenerator; -import com.swirlds.platform.test.gui.GeneratorEventProvider; +import com.swirlds.platform.test.gui.ListEventProvider; import com.swirlds.platform.test.gui.TestGuiSource; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; @@ -16,13 +16,18 @@ /** A type which orchestrates the generation of events and the validation of the consensus output */ public class ConsensusTestOrchestrator { + private final PlatformContext platformContext; private final List nodes; private long currentSequence = 0; private final List weights; private final int totalEventNum; public ConsensusTestOrchestrator( - final List nodes, final List weights, final int totalEventNum) { + final PlatformContext platformContext, + final List nodes, + final List weights, + final int totalEventNum) { + this.platformContext = platformContext; this.nodes = nodes; this.weights = weights; this.totalEventNum = totalEventNum; @@ -49,16 +54,13 @@ private void generateEvents(final int numEvents) { @SuppressWarnings("unused") // useful for debugging public void runGui() { final ConsensusTestNode node = nodes.stream().findAny().orElseThrow(); - - final PlatformContext platformContext = - TestPlatformContextBuilder.create().build(); final AddressBook addressBook = node.getEventEmitter().getGraphGenerator().getAddressBook(); new TestGuiSource( platformContext, addressBook, - new GeneratorEventProvider(node.getEventEmitter().getGraphGenerator())) + new ListEventProvider(node.getOutput().getAddedEvents())) .runGui(); } @@ -136,6 +138,14 @@ public void restartAllNodes() { } } + /** + * Simulates removing a node from the network at restart + * @param nodeId the node to remove + */ + public void removeNode(final NodeId nodeId) { + nodes.forEach(node -> node.removeNode(nodeId)); + } + /** * Configures the graph generators of all nodes with the given configurator. This must be done for all nodes so that * the generators generate the same graphs diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/OrchestratorBuilder.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/OrchestratorBuilder.java index 188f22fb6c09..ff560c5db29a 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/OrchestratorBuilder.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/OrchestratorBuilder.java @@ -7,7 +7,6 @@ import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.common.test.fixtures.ResettableRandom; import com.swirlds.common.test.fixtures.WeightGenerator; -import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.platform.test.event.emitter.EventEmitter; import com.swirlds.platform.test.event.emitter.EventEmitterGenerator; import com.swirlds.platform.test.event.emitter.ShuffledEventEmitter; @@ -17,7 +16,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -29,8 +27,7 @@ public class OrchestratorBuilder { private int totalEventNum = 10_000; private Function, List> eventSourceBuilder = EventSourceFactory::newStandardEventSources; private Consumer eventSourceConfigurator = es -> {}; - private PlatformContext platformContext = - TestPlatformContextBuilder.create().build(); + private PlatformContext platformContext; /** * A function that creates an event emitter based on a graph generator and a seed. They should produce emitters that * will emit events in different orders. For example, nothing would be tested if both returned a @@ -52,17 +49,6 @@ public class OrchestratorBuilder { return this; } - /** - * Set the {@link PlatformContext} to use. If not set, uses a default context. - * - * @param platformContext - * @return this OrchestratorBuilder - */ - public @NonNull OrchestratorBuilder setPlatformContext(@NonNull final PlatformContext platformContext) { - this.platformContext = Objects.requireNonNull(platformContext); - return this; - } - public @NonNull OrchestratorBuilder setTestInput(@NonNull final TestInput testInput) { numberOfNodes = testInput.numberOfNodes(); weightGenerator = testInput.weightGenerator(); @@ -118,6 +104,6 @@ public class OrchestratorBuilder { nodes.add(ConsensusTestNode.genesisContext(platformContext, node1Emitter)); nodes.add(ConsensusTestNode.genesisContext(platformContext, node2Emitter)); - return new ConsensusTestOrchestrator(nodes, weights, totalEventNum); + return new ConsensusTestOrchestrator(platformContext, nodes, weights, totalEventNum); } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/ConsensusRoundValidation.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/ConsensusRoundValidation.java index fde89ec1a74e..e4b4af47ebfa 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/ConsensusRoundValidation.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/ConsensusRoundValidation.java @@ -3,11 +3,16 @@ 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 com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.internal.ConsensusRound; +import com.swirlds.platform.state.MinimumJudgeInfo; +import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.test.consensus.framework.ConsensusOutput; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Iterator; +import java.util.List; import java.util.Objects; public class ConsensusRoundValidation { @@ -24,6 +29,21 @@ public static void validateConsensusRounds(final ConsensusOutput output1, final + "output1 has %d rounds, output2 has %d rounds", output1.getConsensusRounds().size(), output2.getConsensusRounds().size())); + validateAncientThresholdIncreases(output1.getConsensusRounds()); + } + + public static void validateAncientThresholdIncreases(@NonNull final List rounds) { + long lastAncientThreshold = EventConstants.ANCIENT_THRESHOLD_UNDEFINED; + for (final ConsensusRound round : rounds) { + final MinimumJudgeInfo thresholdInfo = + round.getSnapshot().getMinimumJudgeInfoList().getLast(); + assertEquals( + round.getRoundNum(), thresholdInfo.round(), "the last threshold should be for the current round"); + assertTrue( + thresholdInfo.minimumJudgeAncientThreshold() >= lastAncientThreshold, + "the ancient threshold should never decrease"); + lastAncientThreshold = thresholdInfo.minimumJudgeAncientThreshold(); + } } public static void validateIterableRounds( diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/NoEventsLost.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/NoEventsLost.java index ee93d3705705..892fd449b316 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/NoEventsLost.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/validation/NoEventsLost.java @@ -34,10 +34,8 @@ public static void validateNoEventsAreLost( // no consensus reached, nothing to check return; } - final long nonAncientGen = output.getConsensusRounds() - .getLast() - .getSnapshot() - .getMinimumGenerationNonAncient(CONFIG.roundsNonAncient()); + final long nonAncientGen = + output.getConsensusRounds().getLast().getSnapshot().getAncientThreshold(CONFIG.roundsNonAncient()); for (final PlatformEvent event : output.getAddedEvents()) { if (event.getGeneration() >= nonAncientGen) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/AbstractEventEmitter.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/AbstractEventEmitter.java index 7a7c7953c3c1..6a9c51ec9a8f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/AbstractEventEmitter.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/AbstractEventEmitter.java @@ -61,12 +61,4 @@ public void reset() { graphGenerator.reset(); numEventsEmitted = 0; } - - /** - * {@inheritDoc} - */ - @Override - public void setGraphGeneratorSeed(final long seed) { - graphGenerator = graphGenerator.cleanCopy(seed); - } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/EventEmitter.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/EventEmitter.java index ac6407712165..2d17770531f8 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/EventEmitter.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/event/emitter/EventEmitter.java @@ -99,13 +99,4 @@ default void skip(final long numberToSkip) { emitEvent(); } } - - /** - * Creates a clean copy of the underlying {@link GraphGenerator} with the supplied seed, forcing it to create a - * different graph. - * - * @param seed - * the new seed to use - */ - void setGraphGeneratorSeed(final long seed); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/gui/TestGuiSource.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/gui/TestGuiSource.java index 6deb6a81ff1a..6ab9e9e35524 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/gui/TestGuiSource.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/gui/TestGuiSource.java @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.test.gui; -import static com.swirlds.platform.consensus.SyntheticSnapshot.GENESIS_SNAPSHOT; - import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.consensus.SyntheticSnapshot; +import com.swirlds.platform.event.AncientMode; import com.swirlds.platform.event.PlatformEvent; +import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.gui.GuiEventStorage; import com.swirlds.platform.gui.hashgraph.HashgraphGuiSource; import com.swirlds.platform.gui.hashgraph.internal.StandardGuiSource; @@ -25,6 +26,7 @@ public class TestGuiSource { private final HashgraphGuiSource guiSource; private ConsensusSnapshot savedSnapshot; private final GuiEventStorage eventStorage; + private final AncientMode ancientMode; /** * Construct a {@link TestGuiSource} with the given platform context, address book, and event provider. @@ -40,6 +42,10 @@ public TestGuiSource( this.eventStorage = new GuiEventStorage(platformContext.getConfiguration(), addressBook); this.guiSource = new StandardGuiSource(addressBook, eventStorage); this.eventProvider = eventProvider; + this.ancientMode = platformContext + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode(); } public void runGui() { @@ -82,7 +88,7 @@ public void generateEvents(final int numEvents) { final JButton reset = new JButton("Reset"); reset.addActionListener(e -> { eventProvider.reset(); - eventStorage.handleSnapshotOverride(GENESIS_SNAPSHOT); + eventStorage.handleSnapshotOverride(SyntheticSnapshot.getGenesisSnapshot(ancientMode)); updateFameDecidedBelow.run(); }); // snapshots diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestArgs.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestArgs.java index 02c232363c60..afcbf931c5c7 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestArgs.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestArgs.java @@ -36,51 +36,38 @@ public class ConsensusTestArgs { static Stream orderInvarianceTests() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 2, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 2, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 9, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 50, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 50, RANDOM, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(2, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(2, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(50, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(50, RANDOM, RANDOM_WEIGHT_DESC))); } static Stream reconnectSimulation() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 4, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 4, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 10, SINGLE_NODE_STRONG_MINORITY, SINGLE_NODE_STRONG_MINORITY_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 10, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC)), Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 10, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + new ConsensusTestParams(10, SINGLE_NODE_STRONG_MINORITY, SINGLE_NODE_STRONG_MINORITY_DESC)), + Arguments.of(new ConsensusTestParams(10, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(10, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream staleEvent() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 6, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 6, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 6, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(6, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(6, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(6, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC))); } static Stream forkingTests() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 2, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(2, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream partitionTests() { @@ -96,18 +83,14 @@ static Stream partitionTests() { // of the test, not the consensus algorithm. // Arguments.of(new ConsensusTestParams(5, INCREMENTING, // INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 9, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(9, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC))); } static Stream subQuorumPartitionTests() { return Stream.of( + Arguments.of(new ConsensusTestParams(7, BALANCED_REAL_WEIGHT, BALANCED_REAL_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 7, BALANCED_REAL_WEIGHT, BALANCED_REAL_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 9, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 9, ONE_THIRD_ZERO_WEIGHT, ONE_THIRD_NODES_ZERO_WEIGHT_DESC, @@ -119,27 +102,22 @@ static Stream subQuorumPartitionTests() { static Stream cliqueTests() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 4, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 9, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(4, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream variableRateTests() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 2, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(2, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream nodeUsesStaleOtherParents() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 2, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(2, BALANCED, BALANCED_WEIGHT_DESC)), Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC, @@ -147,58 +125,42 @@ static Stream nodeUsesStaleOtherParents() { // than what was previously // set 458078453642476240L)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream nodeProvidesStaleOtherParents() { return Stream.of( - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream quorumOfNodesGoDownTests() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 2, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(2, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream subQuorumOfNodesGoDownTests() { return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 2, BALANCED, BALANCED_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of( - new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(2, BALANCED, BALANCED_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(4, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(9, RANDOM_REAL_WEIGHT, RANDOM_WEIGHT_DESC))); } static Stream ancientEventTests() { - return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 4, BALANCED, BALANCED_WEIGHT_DESC))); + return Stream.of(Arguments.of(new ConsensusTestParams(4, BALANCED, BALANCED_WEIGHT_DESC))); } public static Stream restartWithEventsParams() { return Stream.of( - Arguments.of(new ConsensusTestParams( - DEFAULT_PLATFORM_CONTEXT, 5, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 10, RANDOM, RANDOM_WEIGHT_DESC)), - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 20, RANDOM, RANDOM_WEIGHT_DESC))); - } - - public static Stream migrationTestParams() { - return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 27, RANDOM, RANDOM_WEIGHT_DESC))); + Arguments.of(new ConsensusTestParams(5, INCREMENTING, INCREMENTAL_NODE_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(10, RANDOM, RANDOM_WEIGHT_DESC)), + Arguments.of(new ConsensusTestParams(20, RANDOM, RANDOM_WEIGHT_DESC))); } - public static Stream nodeRemoveTestParams() { - return Stream.of( - Arguments.of(new ConsensusTestParams(DEFAULT_PLATFORM_CONTEXT, 4, RANDOM, RANDOM_WEIGHT_DESC))); + public static Stream nodeRemoveTestParams() { + return Stream.of(new ConsensusTestParams(4, RANDOM, RANDOM_WEIGHT_DESC)); } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java index 60adef072a69..4b76fb8e9f2e 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java @@ -9,10 +9,11 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.Threshold; -import com.swirlds.config.api.ConfigurationBuilder; +import com.swirlds.config.api.Configuration; import com.swirlds.platform.consensus.ConsensusConfig; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.SyntheticSnapshot; +import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.test.consensus.framework.ConsensusTestNode; import com.swirlds.platform.test.consensus.framework.ConsensusTestOrchestrator; @@ -521,38 +522,16 @@ public static void reconnect(@NonNull final TestInput input) { } public static void removeNode(@NonNull final TestInput input) { - final ConsensusTestOrchestrator orchestrator1 = + final ConsensusTestOrchestrator orchestrator = OrchestratorBuilder.builder().setTestInput(input).build(); - orchestrator1.generateEvents(0.5); - orchestrator1.validate( + orchestrator.generateEvents(0.5); + orchestrator.validate( Validations.standard().ratios(EventRatioValidation.blank().setMinimumConsensusRatio(0.5))); - final ConsensusTestOrchestrator orchestrator2 = OrchestratorBuilder.builder() - .setTestInput(input.setNumberOfNodes(input.numberOfNodes() - 1)) - .build(); - for (int i = 0; i < 2; i++) { - orchestrator2 - .getNodes() - .get(i) - .getIntake() - .loadSnapshot(orchestrator1 - .getNodes() - .get(i) - .getIntake() - .getLatestRound() - .getSnapshot()); - final int fi = i; - orchestrator1.getNodes().get(i).getOutput().getAddedEvents().forEach(e -> { - orchestrator2.getNodes().get(fi).getIntake().addEvent(e.copyGossipedData()); - }); - ConsensusUtils.loadEventsIntoGenerator( - orchestrator1.getNodes().get(i).getOutput().getAddedEvents(), - orchestrator2.getNodes().get(i).getEventEmitter().getGraphGenerator(), - orchestrator2.getNodes().get(i).getRandom()); - } + orchestrator.removeNode(orchestrator.getAddressBook().getNodeId(0)); - orchestrator2.generateEvents(0.5); - orchestrator2.validate( + orchestrator.generateEvents(0.5); + orchestrator.validate( // this used to be set to 0.5, but then a test failed because it had a ratio of 0.4999 // the number are a bit arbitrary, but the goal is to validate that events are reaching consensus Validations.standard().ratios(EventRatioValidation.blank().setMinimumConsensusRatio(0.4))); @@ -565,6 +544,7 @@ public static void syntheticSnapshot(@NonNull final TestInput input) { final ConsensusTestOrchestrator orchestrator = OrchestratorBuilder.builder().setTestInput(input).build(); final Instant snapshotTimestamp = Instant.now(); + final Configuration configuration = input.platformContext().getConfiguration(); orchestrator.getNodes().forEach(n -> { final int numEvents = orchestrator.getEventFraction(0.5); n.getEventEmitter().setCheckpoint(numEvents); @@ -576,10 +556,8 @@ public static void syntheticSnapshot(@NonNull final TestInput input) { round, lastConsensusOrder, snapshotTimestamp, - ConfigurationBuilder.create() - .withConfigDataType(ConsensusConfig.class) - .build() - .getConfigData(ConsensusConfig.class), + configuration.getConfigData(ConsensusConfig.class), + configuration.getConfigData(EventConfig.class).getAncientMode(), maxGenEvent.orElseThrow().getBaseEvent()); n.getIntake().loadSnapshot(syntheticSnapshot); }); @@ -602,7 +580,11 @@ public static void genesisSnapshotTest(@NonNull final TestInput input) { final ConsensusTestOrchestrator orchestrator = OrchestratorBuilder.builder().setTestInput(input).build(); for (final ConsensusTestNode node : orchestrator.getNodes()) { - node.getIntake().loadSnapshot(SyntheticSnapshot.getGenesisSnapshot()); + node.getIntake() + .loadSnapshot(SyntheticSnapshot.getGenesisSnapshot(input.platformContext() + .getConfiguration() + .getConfigData(EventConfig.class) + .getAncientMode())); } orchestrator diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestParams.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestParams.java index f1d0d43c0683..5d76acaa5140 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestParams.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestParams.java @@ -1,16 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.test.consensus; -import com.swirlds.common.context.PlatformContext; import com.swirlds.common.test.fixtures.WeightGenerator; import edu.umd.cs.findbugs.annotations.NonNull; public record ConsensusTestParams( - @NonNull PlatformContext platformContext, - int numNodes, - @NonNull WeightGenerator weightGenerator, - @NonNull String weightDesc, - long... seeds) { + int numNodes, @NonNull WeightGenerator weightGenerator, @NonNull String weightDesc, long... seeds) { @Override public String toString() { return numNodes + " nodes, " + weightDesc; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestRunner.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestRunner.java index a6ab13d69ba6..5ba94f733a83 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestRunner.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestRunner.java @@ -1,13 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 package com.swirlds.platform.test.consensus; +import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.test.consensus.framework.TestInput; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; import java.util.Random; import org.junit.jupiter.api.function.ThrowingConsumer; public class ConsensusTestRunner { private ConsensusTestParams params; + private List contexts; private ThrowingConsumer test; private int iterations = 1; private int eventsToGenerate = 10_000; @@ -21,6 +24,11 @@ public class ConsensusTestRunner { return this; } + public @NonNull ConsensusTestRunner setContexts(@NonNull final List contexts) { + this.contexts = contexts; + return this; + } + public @NonNull ConsensusTestRunner setTest(@NonNull final ThrowingConsumer test) { this.test = test; return this; @@ -32,19 +40,27 @@ public class ConsensusTestRunner { } public void run() { - try { - for (final long seed : params.seeds()) { - System.out.println("Running seed: " + seed); + for (final long seed : params.seeds()) { + runWithSeed(seed); + } - test.accept(new TestInput( - params.platformContext(), params.numNodes(), params.weightGenerator(), seed, eventsToGenerate)); - } + if (params.seeds().length > 0) { + // if we are given an explicit seed, we should not run with random seeds + return; + } - for (int i = 0; i < iterations; i++) { - final long seed = new Random().nextLong(); - System.out.println("Running seed: " + seed); - test.accept(new TestInput( - params.platformContext(), params.numNodes(), params.weightGenerator(), seed, eventsToGenerate)); + for (int i = 0; i < iterations; i++) { + final long seed = new Random().nextLong(); + runWithSeed(seed); + } + } + + private void runWithSeed(final long seed) { + System.out.println("Running seed: " + seed); + try { + for (final PlatformContext context : contexts) { + test.accept( + new TestInput(context, params.numNodes(), params.weightGenerator(), seed, eventsToGenerate)); } } catch (final Throwable e) { throw new RuntimeException(e); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java index 594ab3ea04d9..85708347feef 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java @@ -4,16 +4,13 @@ import static com.swirlds.common.test.fixtures.WeightGenerators.RANDOM; import static com.swirlds.platform.test.consensus.ConsensusTestArgs.RANDOM_WEIGHT_DESC; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.test.fixtures.junit.tags.TestComponentTags; -import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.ConsensusImpl; -import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.eventhandling.EventConfig_; import com.swirlds.platform.test.PlatformTest; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Function; +import java.util.List; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -33,11 +30,6 @@ class ConsensusTests extends PlatformTest { private boolean ignoreNoJudgesMarkerFile = false; private boolean ignoreCoinRoundMarkerFile = false; - @BeforeAll - public static void initConfig() { - new TestConfigBuilder().getOrCreateConfig(); - } - @AfterEach void checkForMarkerFiles() { if (!ignoreNoSuperMajorityMarkerFile) { @@ -53,28 +45,19 @@ void checkForMarkerFiles() { } /** - * Replaces the {@link com.swirlds.common.context.PlatformContext} in the test parameters with a new one built from - * the {@link PlatformTest#createPlatformContext(Function, Function)} and preserves the birthRound configuration - * setting. - * - * @param params the test parameters - * @return the modified test parameters + * Create a list of platform contexts to use for testing. + * @return a list of platform contexts */ - @NonNull - private ConsensusTestParams modifyParams(@NonNull final ConsensusTestParams params) { - return new ConsensusTestParams( + private List contexts() { + return List.of( + createPlatformContext( + null, + configBuilder -> + configBuilder.withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, false)), createPlatformContext( null, - configBuilder -> configBuilder.withValue( - EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, - params.platformContext() - .getConfiguration() - .getConfigData(EventConfig.class) - .useBirthRoundAncientThreshold())), - params.numNodes(), - params.weightGenerator(), - params.weightDesc(), - params.seeds()); + configBuilder -> + configBuilder.withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, true))); } @ParameterizedTest @@ -85,7 +68,8 @@ private ConsensusTestParams modifyParams(@NonNull final ConsensusTestParams para void orderInvarianceTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::orderInvarianceTests) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -98,7 +82,8 @@ void orderInvarianceTests(final ConsensusTestParams params) { void reconnectSimulation(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::reconnect) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -111,7 +96,8 @@ void reconnectSimulation(final ConsensusTestParams params) { void staleEvent(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::stale) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -124,7 +110,8 @@ void staleEvent(final ConsensusTestParams params) { void forkingTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::forkingTests) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); // Some forking tests make too many forkers. When there is > 1/3 nodes forking, both no super majority and @@ -141,7 +128,8 @@ void forkingTests(final ConsensusTestParams params) { void partitionTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::partitionTests) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -154,7 +142,8 @@ void partitionTests(final ConsensusTestParams params) { void subQuorumPartitionTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::subQuorumPartitionTests) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -167,7 +156,8 @@ void subQuorumPartitionTests(final ConsensusTestParams params) { void cliqueTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::cliqueTests) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -180,7 +170,8 @@ void cliqueTests(final ConsensusTestParams params) { void variableRateTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::variableRateTests) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -193,7 +184,8 @@ void variableRateTests(final ConsensusTestParams params) { void nodeUsesStaleOtherParents(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::usesStaleOtherParents) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -206,7 +198,8 @@ void nodeUsesStaleOtherParents(final ConsensusTestParams params) { void nodeProvidesStaleOtherParents(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::providesStaleOtherParents) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -219,7 +212,8 @@ void nodeProvidesStaleOtherParents(final ConsensusTestParams params) { void quorumOfNodesGoDownTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::quorumOfNodesGoDown) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -232,7 +226,8 @@ void quorumOfNodesGoDownTests(final ConsensusTestParams params) { void subQuorumOfNodesGoDownTests(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::subQuorumOfNodesGoDown) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -245,7 +240,8 @@ void subQuorumOfNodesGoDownTests(final ConsensusTestParams params) { void repeatedTimestampTest(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::repeatedTimestampTest) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -258,7 +254,8 @@ void repeatedTimestampTest(final ConsensusTestParams params) { void ancientEventTest(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::ancient) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -271,7 +268,8 @@ void ancientEventTest(final ConsensusTestParams params) { void fastRestartWithEvents(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::restart) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -284,7 +282,8 @@ void fastRestartWithEvents(final ConsensusTestParams params) { void nodeRemoveTest(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::removeNode) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -293,7 +292,8 @@ void nodeRemoveTest(final ConsensusTestParams params) { void syntheticSnapshotTest() { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::syntheticSnapshot) - .setParams(new ConsensusTestParams(createDefaultPlatformContext(), 4, RANDOM, RANDOM_WEIGHT_DESC)) + .setParams(new ConsensusTestParams(4, RANDOM, RANDOM_WEIGHT_DESC)) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } @@ -306,7 +306,8 @@ void syntheticSnapshotTest() { void genesisSnapshotTest(final ConsensusTestParams params) { ConsensusTestRunner.create() .setTest(ConsensusTestDefinitions::genesisSnapshotTest) - .setParams(modifyParams(params)) + .setParams(params) + .setContexts(contexts()) .setIterations(NUM_ITER) .run(); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/GraphGeneratorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/GraphGeneratorTests.java index 69b8c663a558..4cd18c0b7943 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/GraphGeneratorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/GraphGeneratorTests.java @@ -421,17 +421,6 @@ public void validateEventOrder(final GraphGenerator generator) { generator.reset(); } - /** - * Make sure the copy constructor that changes the seed works. - */ - public void validateCopyWithNewSeed(final GraphGenerator generator) { - System.out.println("Validate Copy With New Seed"); - final GraphGenerator generator1 = generator.cleanCopy(); - final GraphGenerator generator2 = generator.cleanCopy(1234); - - assertNotEquals(generator1.generateEvents(1000), generator2.generateEvents(1000)); - } - /** * Run a generator through a gauntlet of sanity checks. */ @@ -443,7 +432,6 @@ public void generatorSanityChecks(final GraphGenerator generator) { validateParentDistribution(generator); validateOtherParentDistribution(generator); validateEventOrder(generator); - validateCopyWithNewSeed(generator); validateMaxGeneration(generator); validateBirthRoundAdvancing(generator); } @@ -752,4 +740,34 @@ void repeatedTimestampTests(final boolean birthRoundAsAncientThreshold) { assertTrue(deviation < 0.01, "OOB"); } + + /** + * Tests if the node removal functionality works as expected. + */ + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @Tag(TestComponentTags.PLATFORM) + @Tag(TestComponentTags.CONSENSUS) + @DisplayName("Node Remove Test") + void nodeRemoveTest(final boolean birthRoundAsAncientThreshold) { + final int numberOfEvents = 10_000; + final PlatformContext platformContext = + birthRoundAsAncientThreshold ? BIRTH_ROUND_PLATFORM_CONTEXT : DEFAULT_PLATFORM_CONTEXT; + final StandardGraphGenerator generator = new StandardGraphGenerator( + platformContext, + 0, + new StandardEventSource(), + new StandardEventSource(), + new StandardEventSource(), + new StandardEventSource()); + generator.generateEvents(numberOfEvents / 2); + + final NodeId removalNode = generator.getAddressBook().getNodeId(0); + generator.removeNode(removalNode); + + final List postRemovalEvents = generator.generateEvents(numberOfEvents / 2); + for (final EventImpl removalEvent : postRemovalEvents) { + assertNotEquals(removalNode, removalEvent.getCreatorId()); + } + } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/IntakeAndConsensusTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/IntakeAndConsensusTests.java index 11b59267d4a6..9caeedbd1565 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/IntakeAndConsensusTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/IntakeAndConsensusTests.java @@ -2,29 +2,21 @@ package com.swirlds.platform.test.consensus; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.consensus.ConsensusConfig_; import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.test.consensus.framework.validation.ConsensusRoundValidation; -import com.swirlds.platform.test.fixtures.event.DynamicValue; -import com.swirlds.platform.test.fixtures.event.generator.GraphGenerator; import com.swirlds.platform.test.fixtures.event.generator.StandardGraphGenerator; import com.swirlds.platform.test.fixtures.event.source.EventSource; import com.swirlds.platform.test.fixtures.event.source.StandardEventSource; import com.swirlds.platform.test.graph.OtherParentMatrixFactory; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.time.Instant; import java.util.LinkedList; import java.util.List; -import java.util.Objects; import java.util.stream.Stream; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class IntakeAndConsensusTests { @@ -44,7 +36,6 @@ class IntakeAndConsensusTests { * Tests the workaround described in #5762 */ @Test - @Disabled("This test needs to be investigated") void nonAncientEventWithMissingParents() { final long seed = 0; final int numNodes = 10; @@ -61,7 +52,11 @@ void nonAncientEventWithMissingParents() { // the generated events are first fed into consensus so that round created is calculated before we start // using them - final GeneratorWithConsensus generator = new GeneratorWithConsensus(platformContext, seed, numNodes); + final List eventSources = Stream.generate(StandardEventSource::new) + .map(ses -> (EventSource) ses) + .limit(numNodes) + .toList(); + final StandardGraphGenerator generator = new StandardGraphGenerator(platformContext, seed, eventSources); final TestIntake node1 = new TestIntake(platformContext, generator.getAddressBook()); final TestIntake node2 = new TestIntake(platformContext, generator.getAddressBook()); @@ -69,14 +64,10 @@ void nonAncientEventWithMissingParents() { final int firstBatchSize = 5000; List batch = generator.generateEvents(firstBatchSize); for (final EventImpl event : batch) { - node1.addEvent(event.getBaseEvent()); - node2.addEvent(event.getBaseEvent()); + node1.addEvent(event.getBaseEvent().copyGossipedData()); + node2.addEvent(event.getBaseEvent().copyGossipedData()); } - // System.out.printf("after the first batch of %d events:\n", firstBatchSize); - printGenerations(node1, 1); - printGenerations(node2, 2); - assertConsensusEvents(node1, node2); // now we create a partition @@ -93,7 +84,7 @@ void nonAncientEventWithMissingParents() { EventImpl lastEvent = null; while (!succeeded) { batch = generator.generateEvents(1); - lastEvent = batch.get(0); + lastEvent = batch.getFirst(); if (partitionNodes.contains((int) lastEvent.getCreatorId().id())) { partitionMinGen = partitionMinGen == EventConstants.GENERATION_UNDEFINED ? lastEvent.getGeneration() @@ -101,34 +92,28 @@ void nonAncientEventWithMissingParents() { partitionMaxGen = Math.max(partitionMaxGen, lastEvent.getGeneration()); partitionedEvents.add(lastEvent); } else { - node1.addEvent(lastEvent.getBaseEvent()); + node1.addEvent(lastEvent.getBaseEvent().copyGossipedData()); final long node1NonAncGen = node1.getOutput().getEventWindow().getAncientThreshold(); if (partitionMaxGen > node1NonAncGen && partitionMinGen < node1NonAncGen) { succeeded = true; } else { - node2.addEvent(lastEvent.getBaseEvent()); + node2.addEvent(lastEvent.getBaseEvent().copyGossipedData()); } } } - // System.out.println("after the partition and mini batches:"); - printGenerations(node1, 1); - printGenerations(node2, 2); - // System.out.printf("- partitionMinGen:%d partitionMaxGen:%d\n", partitionMinGen, partitionMaxGen); - // now we insert the minority partition events into both consensus objects, which are in a different state of // consensus - node1.addEvents(partitionedEvents); - node2.addEvents(partitionedEvents); + for (final EventImpl partitionedEvent : partitionedEvents) { + node1.addEvent(partitionedEvent.getBaseEvent().copyGossipedData()); + node2.addEvent(partitionedEvent.getBaseEvent().copyGossipedData()); + } // now we add the event that was added to 1 but not to 2 node2.addEvent(lastEvent.getBaseEvent()); + final long consRoundBeforeLastBatch = + node1.getConsensusRounds().getLast().getRoundNum(); assertConsensusEvents(node1, node2); - // System.out.println("after adding partition events and last mini batch to node 2:"); - printGenerations(node1, 1); - printGenerations(node2, 2); - // System.out.printf("- partitionMinGen:%d partitionMaxGen:%d\n", partitionMinGen, partitionMaxGen); - // now the partitions rejoin generator.setOtherParentAffinity(OtherParentMatrixFactory.createBalancedOtherParentMatrix(numNodes)); @@ -139,9 +124,9 @@ void nonAncientEventWithMissingParents() { node1.addEvent(event.getBaseEvent()); node2.addEvent(event.getBaseEvent()); } - // System.out.println("after the partitions rejoin:"); - printGenerations(node1, 1); - printGenerations(node2, 2); + Assertions.assertTrue( + node1.getConsensusRounds().getLast().getRoundNum() > consRoundBeforeLastBatch, + "consensus did not advance after the partition rejoined"); assertConsensusEvents(node1, node2); } @@ -152,106 +137,4 @@ private static void assertConsensusEvents(final TestIntake node1, final TestInta node1.getConsensusRounds().clear(); node2.getConsensusRounds().clear(); } - - private static void printGenerations(final TestIntake node, final int nodeNum) { - // System.out.printf("- node %d - minRound:%d maxRound:%d\n", - // nodeNum, - // node.getConsensus().getMinRound(), - // node.getConsensus().getMaxRound()); - // System.out.printf("- node %d - minRoundGen:%d nonAncGen:%d maxRoundGen:%d\n", - // nodeNum, - // node.getConsensus().getMinRoundGeneration(), - // node.getConsensus().getMinGenerationNonAncient(), - // node.getConsensus().getMaxRoundGeneration()); - } - - private static class GeneratorWithConsensus implements GraphGenerator { - private final StandardGraphGenerator generator; - private final TestIntake intake; - - @SuppressWarnings("unchecked") - public GeneratorWithConsensus( - @NonNull final PlatformContext platformContext, final long seed, final int numNodes) { - Objects.requireNonNull(platformContext); - final List eventSources = - Stream.generate(StandardEventSource::new).limit(numNodes).toList(); - generator = new StandardGraphGenerator(platformContext, seed, (List) (List) eventSources); - intake = new TestIntake(platformContext, generator.getAddressBook()); - } - - @Override - public EventImpl generateEvent() { - final EventImpl event = generator.generateEvent(); - intake.addEvent(event.getBaseEvent()); - return event; - } - - @Override - public int getNumberOfSources() { - return generator.getNumberOfSources(); - } - - @Override - @Nullable - public EventSource getSource(@NonNull final NodeId nodeID) { - Objects.requireNonNull(nodeID); - return generator.getSource(nodeID); - } - - @Override - public GeneratorWithConsensus cleanCopy() { - throw new UnsupportedOperationException("not implements"); - } - - @Override - public GeneratorWithConsensus cleanCopy(final long seed) { - throw new UnsupportedOperationException("not implements"); - } - - @Override - public void reset() { - throw new UnsupportedOperationException("not implements"); - } - - @Override - public long getNumEventsGenerated() { - return generator.getNumEventsGenerated(); - } - - @Override - public AddressBook getAddressBook() { - return generator.getAddressBook(); - } - - @Override - public long getMaxGeneration(@NonNull final NodeId creatorId) { - Objects.requireNonNull(creatorId); - return generator.getMaxGeneration(creatorId); - } - - @Override - public long getMaxBirthRound(@Nullable final NodeId creatorId) { - return generator.getMaxBirthRound(creatorId); - } - - @Override - public long getMaxGeneration() { - return generator.getMaxGeneration(); - } - - @Override - public void setOtherParentAffinity(final List> affinityMatrix) { - generator.setOtherParentAffinity(affinityMatrix); - } - - @Override - public void setOtherParentAffinity(final DynamicValue>> affinityMatrix) { - generator.setOtherParentAffinity(affinityMatrix); - } - - @Override - public void setPreviousTimestamp(final Instant previousTimestamp) { - generator.setPreviousTimestamp(previousTimestamp); - } - } }