Skip to content

Commit

Permalink
feat: consensus can use birth rounds to determine ancient status (#17907
Browse files Browse the repository at this point in the history
)

Signed-off-by: Lazar Petrovic <[email protected]>
Signed-off-by: Timo Brandstätter <[email protected]>
Co-authored-by: Timo Brandstätter <[email protected]>
  • Loading branch information
lpetrovic05 and timo0 authored Feb 21, 2025
1 parent 007b529 commit acd08f7
Show file tree
Hide file tree
Showing 42 changed files with 624 additions and 699 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -697,7 +704,7 @@ private List<EventImpl> 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
Expand All @@ -723,18 +730,8 @@ private List<EventImpl> 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,
Expand Down Expand Up @@ -862,7 +859,7 @@ private void setConsensusOrder(@NonNull final Collection<EventImpl> 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) {
Expand Down Expand Up @@ -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();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public List<ConsensusRound> 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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<MinimumJudgeInfo> minimumJudgeStorage;
/** a derivative of the only roster currently in use, until roster changes are implemented */
Expand All @@ -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));
Expand All @@ -50,7 +62,8 @@ public void reset() {
minimumJudgeStorage.reset(ConsensusConstants.ROUND_FIRST);
maxRoundCreated = ConsensusConstants.ROUND_UNDEFINED;
roundElections.reset();
updateMinGenNonAncient();
updateAncientThreshold();
consensusRelevantGeneration = EventConstants.GENERATION_UNDEFINED;
}

/**
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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();
}

/**
Expand Down Expand Up @@ -179,7 +200,7 @@ public void loadFromMinimumJudge(@NonNull final List<MinimumJudgeInfo> minimumJu
minimumJudgeStorage.add(minimumJudgeInfo.round(), minimumJudgeInfo);
}
roundElections.setRound(minimumJudgeStorage.getLatest().round() + 1);
updateMinGenNonAncient();
updateAncientThreshold();
}

/**
Expand Down Expand Up @@ -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
*
* <p>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.
*
* <p>Define gen(R) to be the minimum generation of all the events that were famous witnesses in
* round R.
*
* <p>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.
*
* <p>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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}
Loading

0 comments on commit acd08f7

Please sign in to comment.