diff --git a/src/main/java/com/limechain/grandpa/round/CompletedStage.java b/src/main/java/com/limechain/grandpa/round/CompletedStage.java index 114f2efe..2eefd5a5 100644 --- a/src/main/java/com/limechain/grandpa/round/CompletedStage.java +++ b/src/main/java/com/limechain/grandpa/round/CompletedStage.java @@ -8,7 +8,7 @@ public class CompletedStage implements StageState { @Override public void start(GrandpaRound round) { round.setOnFinalizeHandler(null); - round.getOnStageTimerHandler().shutdown(); + round.clearOnStageTimerHandler(); end(round); } diff --git a/src/main/java/com/limechain/grandpa/round/GrandpaRound.java b/src/main/java/com/limechain/grandpa/round/GrandpaRound.java index 41b327af..5057056a 100644 --- a/src/main/java/com/limechain/grandpa/round/GrandpaRound.java +++ b/src/main/java/com/limechain/grandpa/round/GrandpaRound.java @@ -103,6 +103,12 @@ public class GrandpaRound { @Nullable private Vote preCommitChoice; + /** + * Updated when the Grandpa Ghost is calculated.
+ * Default value is false. + */ + private boolean isCompletable; + private Map preVotes = new ConcurrentHashMap<>(); private Map preCommits = new ConcurrentHashMap<>(); private Vote primaryVote; @@ -248,7 +254,7 @@ private boolean isFinalizable() { * * @return if the current round is completable */ - private boolean isCompletable() { + public boolean checkIfCompletable() { Map votes = getDirectVotes(SubRound.PRE_COMMIT); long votesCount = votes.values().stream() @@ -350,7 +356,7 @@ private BlockHeader findBestFinalCandidate() { * * @return GRANDPA GHOST block as a vote */ - private BlockHeader findGrandpaGhost() { + public BlockHeader findGrandpaGhost() { if (roundNumber.equals(BigInteger.ZERO)) { return lastFinalizedBlock; @@ -362,10 +368,11 @@ private BlockHeader findGrandpaGhost() { throw new GhostExecutionException("GHOST not found"); } - BlockHeader grandpaGhost = selectBlockWithMostVotes(blocks, getPrevBestFinalCandidate()); - this.grandpaGhost = grandpaGhost; + BlockHeader result = selectBlockWithMostVotes(blocks, getPrevBestFinalCandidate()); + this.grandpaGhost = result; + this.isCompletable = checkIfCompletable(); - return grandpaGhost; + return result; } /** @@ -650,4 +657,11 @@ public void broadcastCommitMessage() { peerMessageCoordinator.sendCommitMessageToPeers(commitMessage); } + + public void clearOnStageTimerHandler() { + if (onStageTimerHandler != null && onStageTimerHandler.isShutdown()) { + onStageTimerHandler.shutdown(); + onStageTimerHandler = null; + } + } } diff --git a/src/main/java/com/limechain/grandpa/round/PreCommitStage.java b/src/main/java/com/limechain/grandpa/round/PreCommitStage.java index 84ea33d8..f2bf6083 100644 --- a/src/main/java/com/limechain/grandpa/round/PreCommitStage.java +++ b/src/main/java/com/limechain/grandpa/round/PreCommitStage.java @@ -1,13 +1,51 @@ package com.limechain.grandpa.round; +import com.limechain.exception.grandpa.GrandpaGenericException; +import com.limechain.grandpa.vote.SubRound; +import com.limechain.grandpa.vote.Vote; +import lombok.extern.java.Log; + +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +@Log public class PreCommitStage implements StageState { @Override public void start(GrandpaRound round) { + log.fine(String.format("Round %d started pre-commit stage.", round.getRoundNumber())); + + if (round.isCompletable()) { + end(round); + return; + } + + long timeElapsed = System.currentTimeMillis() - round.getStartTime().toEpochMilli(); + long timeRemaining = (4 * GrandpaRound.DURATION) - timeElapsed; + + round.setOnStageTimerHandler(Executors.newScheduledThreadPool(1)); + round.getOnStageTimerHandler().schedule(() -> { + log.fine(String.format("Round %d timer triggered.", round.getRoundNumber())); + end(round); + }, timeRemaining, TimeUnit.MILLISECONDS); } @Override public void end(GrandpaRound round) { - } -} + round.clearOnStageTimerHandler(); + + try { + + Vote grandpaGhost = Vote.fromBlockHeader(round.getGrandpaGhost()); + log.fine(String.format("Round %d ended pre-commit stage.", round.getRoundNumber())); + + round.broadcastVoteMessage(grandpaGhost, SubRound.PRE_COMMIT); + round.setState(new FinalizeStage()); + round.getState().start(round); + + } catch (GrandpaGenericException e) { + log.fine(String.format("Round %d cannot end now: %s", round.getRoundNumber(), e.getMessage())); + } + } +} \ No newline at end of file