From ae41e5cd388f8079448b6fe45d9c0e2db7e935a3 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Tue, 21 Jan 2025 10:04:29 +0200 Subject: [PATCH 01/15] feat: initial implementation --- .../com/limechain/grandpa/GrandpaService.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 82c3e16db..9e9cf9902 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -31,6 +31,36 @@ public GrandpaService(GrandpaSetState grandpaSetState, BlockState blockState) { this.blockState = blockState; } + private void attemptToFinalizeAt(GrandpaRound grandpaRound) { + BigInteger lastFinalizedBlockNumber = blockState.getHighestFinalizedNumber(); + + Vote bestFinalCandidate = grandpaRound.getBestFinalCandidate(); + if (bestFinalCandidate == null) { + return; + } + + var bestFinalCandidateVotesCount = getObservedVotesForBlock( + grandpaRound, + bestFinalCandidate.getBlockHash(), + Subround.PRECOMMIT + ); + + long totalVoters = grandpaSetState.getAuthorities().size(); + long threshold = (2 * totalVoters) / 3; + + // L - last finalized block + // E - best final candidate + // The spec defines E >= L, but here E > L ensures E is new and not already received. + if (bestFinalCandidate.getBlockNumber().compareTo(lastFinalizedBlockNumber) > 0 && + bestFinalCandidateVotesCount > threshold) { + + BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash()); + blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId()); + + //TODO: broadcast + } + } + /** * Determines if the specified round can be finalized. * 1) Checks for a valid preVote candidate and ensures it's completable. From 31233112c8d5c37b841fd3cfcce7dd5311cab2a2 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Tue, 21 Jan 2025 13:53:05 +0200 Subject: [PATCH 02/15] feat: remove setting of ghost and best final candidate from isFinalizable and place it to the methods where they are calculated --- .../java/com/limechain/grandpa/GrandpaService.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 9e9cf9902..251200e61 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -76,8 +76,6 @@ private boolean isFinalizable(GrandpaRound grandpaRound) { return false; } - grandpaRound.setPreVotedBlock(preVoteCandidate); - if (!isCompletable(grandpaRound)) { return false; } @@ -87,8 +85,6 @@ private boolean isFinalizable(GrandpaRound grandpaRound) { return false; } - grandpaRound.setBestFinalCandidate(bestFinalCandidate); - var prevGrandpaRound = grandpaRound.getPrevious(); if (prevGrandpaRound == null) { return false; @@ -201,6 +197,8 @@ public Vote getBestFinalCandidate(GrandpaRound grandpaRound) { } } + grandpaRound.setBestFinalCandidate(bestFinalCandidate); + return bestFinalCandidate; } @@ -224,7 +222,10 @@ public Vote getGrandpaGhost(GrandpaRound grandpaRound) { throw new GhostExecutionException("GHOST not found"); } - return selectBlockWithMostVotes(blocks); + Vote grandpaGhost = selectBlockWithMostVotes(blocks); + grandpaRound.setPreVotedBlock(grandpaGhost); + + return grandpaGhost; } /** From 144ca64349591ef80741db1decec9930c6be7be9 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Tue, 21 Jan 2025 17:58:17 +0200 Subject: [PATCH 03/15] feat: ignore commit messages when we are active voters --- .../java/com/limechain/grandpa/GrandpaService.java | 5 +---- .../com/limechain/grandpa/state/GrandpaSetState.java | 10 ++++++++++ .../network/protocol/grandpa/GrandpaEngine.java | 5 ++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 251200e61..50e87bf4e 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -48,10 +48,7 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { long totalVoters = grandpaSetState.getAuthorities().size(); long threshold = (2 * totalVoters) / 3; - // L - last finalized block - // E - best final candidate - // The spec defines E >= L, but here E > L ensures E is new and not already received. - if (bestFinalCandidate.getBlockNumber().compareTo(lastFinalizedBlockNumber) > 0 && + if (bestFinalCandidate.getBlockNumber().compareTo(lastFinalizedBlockNumber) >= 0 && bestFinalCandidateVotesCount > threshold) { BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash()); diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java index a70bcd983..55b9b0ec6 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java @@ -13,6 +13,8 @@ import com.limechain.storage.DBConstants; import com.limechain.storage.KVRepository; import com.limechain.storage.StateUtil; +import com.limechain.storage.crypto.KeyStore; +import com.limechain.storage.crypto.KeyType; import com.limechain.sync.warpsync.dto.AuthoritySetChange; import com.limechain.sync.warpsync.dto.ForcedAuthoritySetChange; import com.limechain.sync.warpsync.dto.ScheduledAuthoritySetChange; @@ -50,11 +52,14 @@ public class GrandpaSetState { private BigInteger setId; private RoundCache roundCache; + private KeyStore keyStore; + private final PriorityQueue authoritySetChanges = new PriorityQueue<>(AuthoritySetChange.getComparator()); public void initialize() { loadPersistedState(); roundCache = AppBean.getBean(RoundCache.class); + keyStore = AppBean.getBean(KeyStore.class); } /** @@ -230,4 +235,9 @@ public void handleVoteMessage(VoteMessage voteMessage) { } } + public boolean participatesAsVoter() { + return authorities.stream() + .anyMatch(a -> keyStore.getKeyPair(KeyType.GRANDPA, a.getPublicKey()).isPresent()); + } + } \ No newline at end of file diff --git a/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java b/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java index 9c585c43e..24715d9ad 100644 --- a/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java +++ b/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java @@ -152,7 +152,10 @@ private void handleVoteMessage(byte[] message, PeerId peerId) { private void handleCommitMessage(byte[] message, PeerId peerId) { ScaleCodecReader reader = new ScaleCodecReader(message); CommitMessage commitMessage = reader.read(CommitMessageScaleReader.getInstance()); - warpSyncState.syncCommit(commitMessage, peerId); + + if (!grandpaSetState.participatesAsVoter()) { + warpSyncState.syncCommit(commitMessage, peerId); + } } private void handleCatchupRequestMessage(byte[] message, PeerId peerId) { From 8662de5753f5c8a8eaab8d00c53163ed9977e6d0 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Wed, 22 Jan 2025 10:00:55 +0200 Subject: [PATCH 04/15] feat: add commit message archive --- .../com/limechain/grandpa/GrandpaService.java | 4 ++- .../grandpa/state/GrandpaSetState.java | 26 ++++++++++++++++--- .../protocol/grandpa/GrandpaEngine.java | 5 +--- .../sync/warpsync/WarpSyncState.java | 4 ++- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 50e87bf4e..0759d0d60 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -54,7 +54,9 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash()); blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId()); - //TODO: broadcast + if (grandpaSetState.isCommitMessageInArchive(bestFinalCandidate)) { + //TODO: broadcast + } } } diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java index 55b9b0ec6..96886d058 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java @@ -4,6 +4,7 @@ import com.limechain.chain.lightsyncstate.LightSyncState; import com.limechain.exception.grandpa.GrandpaGenericException; import com.limechain.network.protocol.grandpa.messages.catchup.res.SignedVote; +import com.limechain.network.protocol.grandpa.messages.commit.CommitMessage; import com.limechain.network.protocol.grandpa.messages.commit.Vote; import com.limechain.network.protocol.grandpa.messages.consensus.GrandpaConsensusMessage; import com.limechain.network.protocol.grandpa.messages.vote.SignedMessage; @@ -26,8 +27,10 @@ import lombok.extern.java.Log; import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PriorityQueue; @@ -45,16 +48,18 @@ public class GrandpaSetState { private static final BigInteger THRESHOLD_DENOMINATOR = BigInteger.valueOf(3); - private final KVRepository repository; private List authorities; private BigInteger disabledAuthority; private BigInteger setId; private RoundCache roundCache; + private final KVRepository repository; private KeyStore keyStore; - private final PriorityQueue authoritySetChanges = new PriorityQueue<>(AuthoritySetChange.getComparator()); + private final Map> commitMessagesArchive = new HashMap<>(); + private final PriorityQueue authoritySetChanges = + new PriorityQueue<>(AuthoritySetChange.getComparator()); public void initialize() { loadPersistedState(); @@ -151,6 +156,8 @@ public void startNewSet(List authorities) { roundCache.addRound(setId, grandpaRound); this.authorities = authorities; + cleanCommitMessagesArchive(); + log.log(Level.INFO, "Successfully transitioned to authority set id: " + setId); } @@ -198,7 +205,6 @@ public void handleGrandpaConsensusMessage(GrandpaConsensusMessage consensusMessa currentBlockNumber )); case GRANDPA_ON_DISABLED -> disabledAuthority = consensusMessage.getDisabledAuthority(); - //TODO: Implement later case GRANDPA_PAUSE -> log.log(Level.SEVERE, "'PAUSE' grandpa message not implemented"); case GRANDPA_RESUME -> log.log(Level.SEVERE, "'RESUME' grandpa message not implemented"); } @@ -240,4 +246,18 @@ public boolean participatesAsVoter() { .anyMatch(a -> keyStore.getKeyPair(KeyType.GRANDPA, a.getPublicKey()).isPresent()); } + public void addCommitMessageToArchive(CommitMessage message) { + commitMessagesArchive.putIfAbsent(setId, new ArrayList<>()); + List commitMessages = commitMessagesArchive.get(setId); + commitMessages.add(message); + } + + public void cleanCommitMessagesArchive() { + commitMessagesArchive.remove(setId.subtract(BigInteger.TWO)); + } + + public boolean isCommitMessageInArchive(Vote vote) { + return commitMessagesArchive.get(setId).stream() + .noneMatch(cm -> cm.getVote().equals(vote)); + } } \ No newline at end of file diff --git a/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java b/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java index 24715d9ad..9c585c43e 100644 --- a/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java +++ b/src/main/java/com/limechain/network/protocol/grandpa/GrandpaEngine.java @@ -152,10 +152,7 @@ private void handleVoteMessage(byte[] message, PeerId peerId) { private void handleCommitMessage(byte[] message, PeerId peerId) { ScaleCodecReader reader = new ScaleCodecReader(message); CommitMessage commitMessage = reader.read(CommitMessageScaleReader.getInstance()); - - if (!grandpaSetState.participatesAsVoter()) { - warpSyncState.syncCommit(commitMessage, peerId); - } + warpSyncState.syncCommit(commitMessage, peerId); } private void handleCatchupRequestMessage(byte[] message, PeerId peerId) { diff --git a/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java b/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java index b656e07b0..a29d221b8 100644 --- a/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java +++ b/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java @@ -143,7 +143,9 @@ public synchronized void syncCommit(CommitMessage commitMessage, PeerId peerId) return; } - if (warpSyncFinished) { + grandpaSetState.addCommitMessageToArchive(commitMessage); + + if (warpSyncFinished && !grandpaSetState.participatesAsVoter()) { updateState(commitMessage); } } From fe3ae1809729261bf54c84acf03330b66a1bd9da Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Wed, 22 Jan 2025 10:56:49 +0200 Subject: [PATCH 05/15] chore: refactor --- src/main/java/com/limechain/grandpa/GrandpaService.java | 2 +- src/main/java/com/limechain/grandpa/state/GrandpaSetState.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 0759d0d60..326fd8dd1 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -54,7 +54,7 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash()); blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId()); - if (grandpaSetState.isCommitMessageInArchive(bestFinalCandidate)) { + if (!grandpaSetState.isCommitMessageInArchive(bestFinalCandidate)) { //TODO: broadcast } } diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java index 96886d058..d68f4a1f8 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java @@ -258,6 +258,6 @@ public void cleanCommitMessagesArchive() { public boolean isCommitMessageInArchive(Vote vote) { return commitMessagesArchive.get(setId).stream() - .noneMatch(cm -> cm.getVote().equals(vote)); + .anyMatch(cm -> cm.getVote().equals(vote)); } } \ No newline at end of file From 923c7256accbfc27eae2a3fc561cfa7a68dfe9b8 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Wed, 22 Jan 2025 13:15:06 +0200 Subject: [PATCH 06/15] feat: substitute all 2/3 Voters calculations with getThreshold() --- .../com/limechain/grandpa/GrandpaService.java | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 326fd8dd1..37638f383 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -39,17 +39,14 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { return; } - var bestFinalCandidateVotesCount = getObservedVotesForBlock( - grandpaRound, - bestFinalCandidate.getBlockHash(), - Subround.PRECOMMIT + var bestFinalCandidateVotesCount = BigInteger.valueOf( + getObservedVotesForBlock(grandpaRound, bestFinalCandidate.getBlockHash(), Subround.PRECOMMIT) ); - long totalVoters = grandpaSetState.getAuthorities().size(); - long threshold = (2 * totalVoters) / 3; + var threshold = grandpaSetState.getThreshold(); if (bestFinalCandidate.getBlockNumber().compareTo(lastFinalizedBlockNumber) >= 0 && - bestFinalCandidateVotesCount > threshold) { + bestFinalCandidateVotesCount.compareTo(threshold) >= 0) { BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash()); blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId()); @@ -112,10 +109,10 @@ private boolean isCompletable(GrandpaRound grandpaRound) { .sum(); long equivocationsCount = grandpaRound.getPcEquivocationsCount(); - long totalVoters = grandpaSetState.getAuthorities().size(); - long threshold = (2 * totalVoters) / 3; + var totalVotesIncludingEquivocations = BigInteger.valueOf(votesCount + equivocationsCount); + var threshold = grandpaSetState.getThreshold(); - if (votesCount + equivocationsCount <= threshold) { + if (totalVotesIncludingEquivocations.compareTo(threshold) < 0) { return false; } @@ -133,7 +130,11 @@ private boolean isCompletable(GrandpaRound grandpaRound) { Subround.PRECOMMIT ); - if (votesCount - equivocationsCount - observedVotesForDescendantBlock <= threshold) { + var validVotesForThresholdCheck = BigInteger.valueOf( + votesCount - equivocationsCount - observedVotesForDescendantBlock + ); + + if (validVotesForThresholdCheck.compareTo(threshold) < 0) { return false; } } @@ -158,7 +159,11 @@ public Vote getBestFinalCandidate(GrandpaRound grandpaRound) { } var threshold = grandpaSetState.getThreshold(); - Map possibleSelectedBlocks = getPossibleSelectedBlocks(grandpaRound, threshold, Subround.PRECOMMIT); + Map possibleSelectedBlocks = getPossibleSelectedBlocks( + grandpaRound, + threshold, + Subround.PRECOMMIT + ); if (possibleSelectedBlocks.isEmpty()) { return preVoteCandidate; @@ -288,7 +293,10 @@ private Vote selectBlockWithMostVotes(Map blocks) { * @param subround stage of the GRANDPA process, such as PREVOTE, PRECOMMIT or PRIMARY_PROPOSAL. * @return blocks that exceed the required vote threshold */ - private Map getPossibleSelectedBlocks(GrandpaRound grandpaRound, BigInteger threshold, Subround subround) { + private Map getPossibleSelectedBlocks(GrandpaRound grandpaRound, + BigInteger threshold, + Subround subround) { + var votes = getDirectVotes(grandpaRound, subround); var blocks = new HashMap(); @@ -307,7 +315,13 @@ private Map getPossibleSelectedBlocks(GrandpaRound grandpaR List allVotes = getVotes(grandpaRound, subround); for (Vote vote : votes.keySet()) { blocks = new HashMap<>( - getPossibleSelectedAncestors(grandpaRound, allVotes, vote.getBlockHash(), blocks, subround, threshold) + getPossibleSelectedAncestors(grandpaRound, + allVotes, + vote.getBlockHash(), + blocks, + subround, + threshold + ) ); } @@ -358,7 +372,13 @@ private Map getPossibleSelectedAncestors(GrandpaRound grand } else { // Recursively process ancestors - selected = getPossibleSelectedAncestors(grandpaRound, votes, ancestorBlockHash, selected, subround, threshold); + selected = getPossibleSelectedAncestors(grandpaRound, + votes, + ancestorBlockHash, + selected, + subround, + threshold + ); } } From 7e94686aae930f579143d18727047150add06667 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Wed, 22 Jan 2025 15:09:55 +0200 Subject: [PATCH 07/15] feat: rename methods in grandpaService and add exception to grandpaRound getters when fields are null --- .../com/limechain/grandpa/GrandpaService.java | 18 ++++----- .../limechain/grandpa/state/GrandpaRound.java | 11 +++++ .../limechain/grandpa/GrandpaServiceTest.java | 40 +++++++++---------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 493ccab5a..aa366e508 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -35,9 +35,6 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { BigInteger lastFinalizedBlockNumber = blockState.getHighestFinalizedNumber(); Vote bestFinalCandidate = grandpaRound.getBestFinalCandidate(); - if (bestFinalCandidate == null) { - return; - } var bestFinalCandidateVotesCount = BigInteger.valueOf( getObservedVotesForBlock(grandpaRound, bestFinalCandidate.getBlockHash(), Subround.PRECOMMIT) @@ -67,7 +64,7 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { */ private boolean isFinalizable(GrandpaRound grandpaRound) { - Vote preVoteCandidate = getGrandpaGhost(grandpaRound); + Vote preVoteCandidate = findGrandpaGhost(grandpaRound); if (preVoteCandidate == null) { return false; } @@ -76,7 +73,7 @@ private boolean isFinalizable(GrandpaRound grandpaRound) { return false; } - Vote bestFinalCandidate = getBestFinalCandidate(grandpaRound); + Vote bestFinalCandidate = findBestFinalCandidate(grandpaRound); if (bestFinalCandidate == null) { return false; } @@ -150,9 +147,9 @@ private boolean isCompletable(GrandpaRound grandpaRound) { * * @return the best final candidate block */ - public Vote getBestFinalCandidate(GrandpaRound grandpaRound) { + public Vote findBestFinalCandidate(GrandpaRound grandpaRound) { - Vote preVoteCandidate = getGrandpaGhost(grandpaRound); + Vote preVoteCandidate = findGrandpaGhost(grandpaRound); if (grandpaRound.getRoundNumber().equals(BigInteger.ZERO)) { return preVoteCandidate; @@ -213,7 +210,7 @@ public Vote getBestFinalCandidate(GrandpaRound grandpaRound) { * * @return GRANDPA GHOST block as a vote */ - public Vote getGrandpaGhost(GrandpaRound grandpaRound) { + public Vote findGrandpaGhost(GrandpaRound grandpaRound) { var threshold = grandpaSetState.getThreshold(); if (grandpaRound.getRoundNumber().equals(BigInteger.ZERO)) { @@ -241,12 +238,13 @@ public Vote getGrandpaGhost(GrandpaRound grandpaRound) { * * @return the best pre-voted block */ - public Vote getBestPreVoteCandidate(GrandpaRound grandpaRound) { + public Vote findBestPreVoteCandidate(GrandpaRound grandpaRound) { + Vote previousBestFinalCandidate = grandpaRound.getPrevious() != null ? grandpaRound.getPrevious().getBestFinalCandidate() : new Vote(null, BigInteger.ZERO); - Vote currentVote = getGrandpaGhost(grandpaRound); + Vote currentVote = findGrandpaGhost(grandpaRound); SignedVote primaryVote = grandpaRound.getPrimaryVote(); if (primaryVote != null) { diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaRound.java b/src/main/java/com/limechain/grandpa/state/GrandpaRound.java index b6739b7c8..4f3711a87 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaRound.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaRound.java @@ -1,5 +1,6 @@ package com.limechain.grandpa.state; +import com.limechain.exception.grandpa.GrandpaGenericException; import com.limechain.network.protocol.grandpa.messages.catchup.res.SignedVote; import com.limechain.network.protocol.grandpa.messages.commit.Vote; import io.emeraldpay.polkaj.types.Hash256; @@ -29,6 +30,16 @@ public class GrandpaRound implements Serializable { private Map> pvEquivocations = new ConcurrentHashMap<>(); private Map> pcEquivocations = new ConcurrentHashMap<>(); + public Vote getPreVotedBlock() { + if (preVotedBlock == null) throw new GrandpaGenericException("Pre-voted block has not been set"); + return preVotedBlock; + } + + public Vote getBestFinalCandidate() { + if (bestFinalCandidate == null) throw new GrandpaGenericException("Best final candidate has not been set"); + return bestFinalCandidate; + } + public long getPvEquivocationsCount() { return this.pvEquivocations.values().stream() .mapToLong(Set::size) diff --git a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java index a9d1753c4..6b55c3d05 100644 --- a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java +++ b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java @@ -69,7 +69,7 @@ void tearDown() { } @Test - void testGetBestFinalCandidateWithoutPreCommits() { + void testFindBestFinalCandidateWithoutPreCommits() { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); @@ -94,14 +94,14 @@ void testGetBestFinalCandidateWithoutPreCommits() { when(blockState.isDescendantOf(firstVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(secondVote.getBlockHash(), secondVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.getBestFinalCandidate(grandpaRound); + Vote result = grandpaService.findBestFinalCandidate(grandpaRound); assertNotNull(result); assertEquals(firstVote.getBlockHash(), result.getBlockHash()); } @Test - void testGetBestFinalCandidateWithPreCommitBlockNumberBiggerThatPreVoteBlockNumber() { + void testFindBestFinalCandidateWithPreCommitBlockNumberBiggerThatPreVoteBlockNumber() { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(4)); Vote thirdVote = new Vote(new Hash256(TWOS_ARRAY), BigInteger.valueOf(5)); @@ -134,14 +134,14 @@ void testGetBestFinalCandidateWithPreCommitBlockNumberBiggerThatPreVoteBlockNumb when(blockState.isDescendantOf(thirdVote.getBlockHash(), thirdVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(thirdVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.getBestFinalCandidate(grandpaRound); + Vote result = grandpaService.findBestFinalCandidate(grandpaRound); assertNotNull(result); assertEquals(thirdVote.getBlockHash(), result.getBlockHash()); } @Test - void testGetBestFinalCandidateWithPreCommitBlockNumberLessThatPreVoteBlockNumber() { + void testFindBestFinalCandidateWithPreCommitBlockNumberLessThatPreVoteBlockNumber() { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(4)); Vote thirdVote = new Vote(new Hash256(TWOS_ARRAY), BigInteger.valueOf(5)); @@ -175,26 +175,26 @@ void testGetBestFinalCandidateWithPreCommitBlockNumberLessThatPreVoteBlockNumber when(blockState.isDescendantOf(thirdVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(thirdVote.getBlockHash(), blockHeader.getHash())).thenReturn(true); - Vote result = grandpaService.getBestFinalCandidate(grandpaRound); + Vote result = grandpaService.findBestFinalCandidate(grandpaRound); assertNotNull(result); assertEquals(blockHeader.getHash(), result.getBlockHash()); } @Test - void testGetBestFinalCandidateWhereRoundNumberIsZero() { + void testFindBestFinalCandidateWhereRoundNumberIsZero() { BlockHeader blockHeader = createBlockHeader(); when(grandpaRound.getRoundNumber()).thenReturn(BigInteger.valueOf(0)); when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); - var result = grandpaService.getBestFinalCandidate(grandpaRound); + var result = grandpaService.findBestFinalCandidate(grandpaRound); assertEquals(blockHeader.getHash(), result.getBlockHash()); assertEquals(blockHeader.getBlockNumber(), result.getBlockNumber()); } @Test - void testGetBestPreVoteCandidate_WithSignedMessage() { + void testFindBestPreVoteCandidate_WithSignedMessage() { Vote currentVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Hash256 currentVoteAuthorityHash = new Hash256(ONES_ARRAY); SignedVote currentSignedVote = new SignedVote(currentVote, Hash512.empty(), currentVoteAuthorityHash); @@ -225,7 +225,7 @@ void testGetBestPreVoteCandidate_WithSignedMessage() { when(signedMessage.getBlockNumber()).thenReturn(BigInteger.valueOf(4)); when(signedMessage.getBlockHash()).thenReturn(new Hash256(TWOS_ARRAY)); - Vote result = grandpaService.getBestPreVoteCandidate(grandpaRound); + Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); assertNotNull(result); @@ -234,7 +234,7 @@ void testGetBestPreVoteCandidate_WithSignedMessage() { } @Test - void testGetBestPreVoteCandidate_WithoutSignedMessage() { + void testFindBestPreVoteCandidate_WithoutSignedMessage() { Vote currentVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Hash256 currentVoteAuthorityHash = new Hash256(ONES_ARRAY); SignedVote currentSignedVote = new SignedVote(currentVote, Hash512.empty(), currentVoteAuthorityHash); @@ -257,7 +257,7 @@ void testGetBestPreVoteCandidate_WithoutSignedMessage() { when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); when(blockState.isDescendantOf(currentVote.getBlockHash(), currentVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.getBestPreVoteCandidate(grandpaRound); + Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); assertNotNull(result); assertEquals(currentVote.getBlockHash(), result.getBlockHash()); @@ -265,7 +265,7 @@ void testGetBestPreVoteCandidate_WithoutSignedMessage() { } @Test - void testGetBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrentVote() { + void testFindBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrentVote() { Vote currentVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(4)); Hash256 currentVoteAuthorityHash = new Hash256(ONES_ARRAY); SignedVote currentSignedVote = new SignedVote(currentVote, Hash512.empty(), currentVoteAuthorityHash); @@ -291,7 +291,7 @@ void testGetBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrentV when(signedMessage.getBlockNumber()).thenReturn(BigInteger.valueOf(3)); when(signedMessage.getBlockHash()).thenReturn(new Hash256(TWOS_ARRAY)); - Vote result = grandpaService.getBestPreVoteCandidate(grandpaRound); + Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); assertNotNull(result); assertEquals(currentVote.getBlockHash(), result.getBlockHash()); @@ -299,27 +299,27 @@ void testGetBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrentV } @Test - void testGetGrandpaGHOSTWhereNoBlocksPassThreshold() { + void testFindGrandpaGHOSTWhereNoBlocksPassThreshold() { when(grandpaSetState.getThreshold()).thenReturn(BigInteger.valueOf(10)); when(grandpaRound.getRoundNumber()).thenReturn(BigInteger.valueOf(1)); when(grandpaRound.getPreVotes()).thenReturn(Map.of()); - assertThrows(GhostExecutionException.class, () -> grandpaService.getGrandpaGhost(grandpaRound)); + assertThrows(GhostExecutionException.class, () -> grandpaService.findGrandpaGhost(grandpaRound)); } @Test - void testGetGrandpaGHOSTWhereRoundNumberIsZero() { + void testFindGrandpaGHOSTWhereRoundNumberIsZero() { BlockHeader blockHeader = createBlockHeader(); when(grandpaRound.getRoundNumber()).thenReturn(BigInteger.valueOf(0)); when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); - var result = grandpaService.getGrandpaGhost(grandpaRound); + var result = grandpaService.findGrandpaGhost(grandpaRound); assertEquals(blockHeader.getHash(), result.getBlockHash()); assertEquals(blockHeader.getBlockNumber(), result.getBlockNumber()); } @Test - void testGetGrandpaGHOSTWithBlockPassingThreshold() { + void testFindGrandpaGHOSTWithBlockPassingThreshold() { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); @@ -343,7 +343,7 @@ void testGetGrandpaGHOSTWithBlockPassingThreshold() { when(blockState.isDescendantOf(firstVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(secondVote.getBlockHash(), secondVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.getGrandpaGhost(grandpaRound); + Vote result = grandpaService.findGrandpaGhost(grandpaRound); assertNotNull(result); assertEquals(firstVote.getBlockHash(), result.getBlockHash()); } From e48898bafd0c08171e39985ffcf65e401460e347 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Wed, 22 Jan 2025 15:57:38 +0200 Subject: [PATCH 08/15] chore: fix failing tests --- .../java/com/limechain/grandpa/GrandpaServiceTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java index 6b55c3d05..81f3bad57 100644 --- a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java +++ b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java @@ -254,6 +254,11 @@ void testFindBestPreVoteCandidate_WithoutSignedMessage() { currentVoteAuthorityHash, currentSignedVote )); + Vote bfc = new Vote(new Hash256(THREES_ARRAY), BigInteger.ONE); + GrandpaRound previousRound = new GrandpaRound(); + previousRound.setBestFinalCandidate(bfc); + when(grandpaRound.getPrevious()).thenReturn(previousRound); + when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); when(blockState.isDescendantOf(currentVote.getBlockHash(), currentVote.getBlockHash())).thenReturn(true); @@ -286,6 +291,11 @@ void testFindBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrent currentVoteAuthorityHash, currentSignedVote )); + Vote bfc = new Vote(new Hash256(THREES_ARRAY), BigInteger.ONE); + GrandpaRound previousRound = new GrandpaRound(); + previousRound.setBestFinalCandidate(bfc); + when(grandpaRound.getPrevious()).thenReturn(previousRound); + when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); when(blockState.isDescendantOf(currentVote.getBlockHash(), currentVote.getBlockHash())).thenReturn(true); when(signedMessage.getBlockNumber()).thenReturn(BigInteger.valueOf(3)); From c98de01cce868f2c541a0de197661b9b9735d39e Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 09:26:59 +0200 Subject: [PATCH 09/15] chore: add comment to cleanCommitMessageArchive --- src/main/java/com/limechain/grandpa/state/GrandpaSetState.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java index 7b7fa4b01..0a0cb43d5 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java @@ -249,6 +249,8 @@ public void addCommitMessageToArchive(CommitMessage message) { commitMessages.add(message); } + // Removes commit messages two SetIds behind the current one, + // keeping only those received in the current and previous set. public void cleanCommitMessagesArchive() { commitMessagesArchive.remove(setId.subtract(BigInteger.TWO)); } From 187580cbc08b91fe908810ff980410a02d7c91c6 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 09:32:47 +0200 Subject: [PATCH 10/15] feat: add broadcasting of commit msg to attept-to-finalize method --- src/main/java/com/limechain/grandpa/GrandpaService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 01af2f21c..7a8c57078 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -56,7 +56,7 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId()); if (!grandpaSetState.isCommitMessageInArchive(bestFinalCandidate)) { - //TODO: broadcast + broadcastCommitMessage(grandpaRound); } } } @@ -486,7 +486,7 @@ private Vote getLastFinalizedBlockAsVote() { * 2. During attempt-to-finalize, broadcasting a commit message for the best candidate block of the current round. */ public void broadcastCommitMessage(GrandpaRound grandpaRound) { - Vote bestCandidate = getBestFinalCandidate(grandpaRound); + Vote bestCandidate = findBestFinalCandidate(grandpaRound); PreCommit[] precommits = transformToCompactJustificationFormat(grandpaRound.getPreCommits()); CommitMessage commitMessage = new CommitMessage(); From 85e53499ba1e0c1b97f10f1a047329ff8a18babd Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 09:39:48 +0200 Subject: [PATCH 11/15] chore: make findBestFinalCandidate private --- .../com/limechain/grandpa/GrandpaService.java | 2 +- .../limechain/grandpa/GrandpaServiceTest.java | 36 ++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 7a8c57078..199734d81 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -154,7 +154,7 @@ private boolean isCompletable(GrandpaRound grandpaRound) { * * @return the best final candidate block */ - public Vote findBestFinalCandidate(GrandpaRound grandpaRound) { + private Vote findBestFinalCandidate(GrandpaRound grandpaRound) { Vote preVoteCandidate = findGrandpaGhost(grandpaRound); diff --git a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java index f95cb4e1c..029e13680 100644 --- a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java +++ b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java @@ -77,7 +77,7 @@ void tearDown() { } @Test - void testFindBestFinalCandidateWithoutPreCommits() { + void testFindBestFinalCandidateWithoutPreCommits() throws Exception { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); @@ -102,14 +102,18 @@ void testFindBestFinalCandidateWithoutPreCommits() { when(blockState.isDescendantOf(firstVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(secondVote.getBlockHash(), secondVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.findBestFinalCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestFinalCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); assertNotNull(result); assertEquals(firstVote.getBlockHash(), result.getBlockHash()); } @Test - void testFindBestFinalCandidateWithPreCommitBlockNumberBiggerThatPreVoteBlockNumber() { + void testFindBestFinalCandidateWithPreCommitBlockNumberBiggerThatPreVoteBlockNumber() throws Exception { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(4)); Vote thirdVote = new Vote(new Hash256(TWOS_ARRAY), BigInteger.valueOf(5)); @@ -142,14 +146,19 @@ void testFindBestFinalCandidateWithPreCommitBlockNumberBiggerThatPreVoteBlockNum when(blockState.isDescendantOf(thirdVote.getBlockHash(), thirdVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(thirdVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.findBestFinalCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestFinalCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); + assertNotNull(result); assertEquals(thirdVote.getBlockHash(), result.getBlockHash()); } @Test - void testFindBestFinalCandidateWithPreCommitBlockNumberLessThatPreVoteBlockNumber() { + void testFindBestFinalCandidateWithPreCommitBlockNumberLessThatPreVoteBlockNumber() throws Exception { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(4)); Vote thirdVote = new Vote(new Hash256(TWOS_ARRAY), BigInteger.valueOf(5)); @@ -183,20 +192,30 @@ void testFindBestFinalCandidateWithPreCommitBlockNumberLessThatPreVoteBlockNumbe when(blockState.isDescendantOf(thirdVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(thirdVote.getBlockHash(), blockHeader.getHash())).thenReturn(true); - Vote result = grandpaService.findBestFinalCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestFinalCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); + assertNotNull(result); assertEquals(blockHeader.getHash(), result.getBlockHash()); } @Test - void testFindBestFinalCandidateWhereRoundNumberIsZero() { + void testFindBestFinalCandidateWhereRoundNumberIsZero() throws Exception { BlockHeader blockHeader = createBlockHeader(); when(grandpaRound.getRoundNumber()).thenReturn(BigInteger.valueOf(0)); when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); - var result = grandpaService.findBestFinalCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestFinalCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); + assertEquals(blockHeader.getHash(), result.getBlockHash()); assertEquals(blockHeader.getBlockNumber(), result.getBlockNumber()); } @@ -235,7 +254,6 @@ void testFindBestPreVoteCandidate_WithSignedMessage() { Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); - assertNotNull(result); assertEquals(new Hash256(TWOS_ARRAY), result.getBlockHash()); assertEquals(BigInteger.valueOf(4), result.getBlockNumber()); From 9a2a38a1160c77b7532b15479d0146ddcaedce65 Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 09:50:47 +0200 Subject: [PATCH 12/15] chore: make findGrandpaGhost and findBestPreVoteCandidate private --- .../com/limechain/grandpa/GrandpaService.java | 4 +- .../limechain/grandpa/GrandpaServiceTest.java | 59 ++++++++++++++----- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 199734d81..eb81637b3 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -217,7 +217,7 @@ private Vote findBestFinalCandidate(GrandpaRound grandpaRound) { * * @return GRANDPA GHOST block as a vote */ - public Vote findGrandpaGhost(GrandpaRound grandpaRound) { + private Vote findGrandpaGhost(GrandpaRound grandpaRound) { var threshold = grandpaSetState.getThreshold(); if (grandpaRound.getRoundNumber().equals(BigInteger.ZERO)) { @@ -245,7 +245,7 @@ public Vote findGrandpaGhost(GrandpaRound grandpaRound) { * * @return the best pre-voted block */ - public Vote findBestPreVoteCandidate(GrandpaRound grandpaRound) { + private Vote findBestPreVoteCandidate(GrandpaRound grandpaRound) { Vote previousBestFinalCandidate = grandpaRound.getPrevious() != null ? grandpaRound.getPrevious().getBestFinalCandidate() diff --git a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java index 029e13680..699c2d61f 100644 --- a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java +++ b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java @@ -1,6 +1,6 @@ package com.limechain.grandpa; -import com.limechain.exception.grandpa.GhostExecutionException; +import com.limechain.exception.grandpa.GrandpaGenericException; import com.limechain.grandpa.state.GrandpaRound; import com.limechain.grandpa.state.GrandpaSetState; import com.limechain.network.PeerMessageCoordinator; @@ -24,6 +24,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigInteger; import java.util.HashMap; @@ -32,8 +33,8 @@ import java.util.Set; import static com.limechain.utils.TestUtils.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -221,7 +222,7 @@ void testFindBestFinalCandidateWhereRoundNumberIsZero() throws Exception { } @Test - void testFindBestPreVoteCandidate_WithSignedMessage() { + void testFindBestPreVoteCandidate_WithSignedMessage() throws Exception { Vote currentVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Hash256 currentVoteAuthorityHash = new Hash256(ONES_ARRAY); SignedVote currentSignedVote = new SignedVote(currentVote, Hash512.empty(), currentVoteAuthorityHash); @@ -252,7 +253,11 @@ void testFindBestPreVoteCandidate_WithSignedMessage() { when(signedMessage.getBlockNumber()).thenReturn(BigInteger.valueOf(4)); when(signedMessage.getBlockHash()).thenReturn(new Hash256(TWOS_ARRAY)); - Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestPreVoteCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); assertNotNull(result); assertEquals(new Hash256(TWOS_ARRAY), result.getBlockHash()); @@ -260,7 +265,7 @@ void testFindBestPreVoteCandidate_WithSignedMessage() { } @Test - void testFindBestPreVoteCandidate_WithoutSignedMessage() { + void testFindBestPreVoteCandidate_WithoutSignedMessage() throws Exception { Vote currentVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Hash256 currentVoteAuthorityHash = new Hash256(ONES_ARRAY); SignedVote currentSignedVote = new SignedVote(currentVote, Hash512.empty(), currentVoteAuthorityHash); @@ -288,7 +293,11 @@ void testFindBestPreVoteCandidate_WithoutSignedMessage() { when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); when(blockState.isDescendantOf(currentVote.getBlockHash(), currentVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestPreVoteCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); assertNotNull(result); assertEquals(currentVote.getBlockHash(), result.getBlockHash()); @@ -296,7 +305,7 @@ void testFindBestPreVoteCandidate_WithoutSignedMessage() { } @Test - void testFindBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrentVote() { + void testFindBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrentVote() throws Exception { Vote currentVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(4)); Hash256 currentVoteAuthorityHash = new Hash256(ONES_ARRAY); SignedVote currentSignedVote = new SignedVote(currentVote, Hash512.empty(), currentVoteAuthorityHash); @@ -327,7 +336,11 @@ void testFindBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrent when(signedMessage.getBlockNumber()).thenReturn(BigInteger.valueOf(3)); when(signedMessage.getBlockHash()).thenReturn(new Hash256(TWOS_ARRAY)); - Vote result = grandpaService.findBestPreVoteCandidate(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findBestPreVoteCandidate", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); assertNotNull(result); assertEquals(currentVote.getBlockHash(), result.getBlockHash()); @@ -335,27 +348,40 @@ void testFindBestPreVoteCandidate_WithSignedMessageAndBlockNumberLessThanCurrent } @Test - void testFindGrandpaGHOSTWhereNoBlocksPassThreshold() { + void testFindGrandpaGHOSTWhereNoBlocksPassThreshold() throws Exception { when(grandpaSetState.getThreshold()).thenReturn(BigInteger.valueOf(10)); when(grandpaRound.getRoundNumber()).thenReturn(BigInteger.valueOf(1)); when(grandpaRound.getPreVotes()).thenReturn(Map.of()); - assertThrows(GhostExecutionException.class, () -> grandpaService.findGrandpaGhost(grandpaRound)); + + Method method = GrandpaService.class.getDeclaredMethod("findGrandpaGhost", GrandpaRound.class); + method.setAccessible(true); + + try { + method.invoke(grandpaService, grandpaRound); + } catch (InvocationTargetException e) { + assertInstanceOf(GrandpaGenericException.class, e.getCause()); + } } @Test - void testFindGrandpaGHOSTWhereRoundNumberIsZero() { + void testFindGrandpaGHOSTWhereRoundNumberIsZero() throws Exception { BlockHeader blockHeader = createBlockHeader(); when(grandpaRound.getRoundNumber()).thenReturn(BigInteger.valueOf(0)); when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); - var result = grandpaService.findGrandpaGhost(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findGrandpaGhost", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); + assertEquals(blockHeader.getHash(), result.getBlockHash()); assertEquals(blockHeader.getBlockNumber(), result.getBlockNumber()); } @Test - void testFindGrandpaGHOSTWithBlockPassingThreshold() { + void testFindGrandpaGHOSTWithBlockPassingThreshold() throws Exception { Vote firstVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); Vote secondVote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(3)); @@ -379,7 +405,12 @@ void testFindGrandpaGHOSTWithBlockPassingThreshold() { when(blockState.isDescendantOf(firstVote.getBlockHash(), firstVote.getBlockHash())).thenReturn(true); when(blockState.isDescendantOf(secondVote.getBlockHash(), secondVote.getBlockHash())).thenReturn(true); - Vote result = grandpaService.findGrandpaGhost(grandpaRound); + // Call the private method via reflection + Method method = GrandpaService.class.getDeclaredMethod("findGrandpaGhost", GrandpaRound.class); + method.setAccessible(true); + + Vote result = (Vote) method.invoke(grandpaService, grandpaRound); + assertNotNull(result); assertEquals(firstVote.getBlockHash(), result.getBlockHash()); } From b0d918ba3174575145bcd0c96984265255be621d Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 09:54:16 +0200 Subject: [PATCH 13/15] chore: make broadcastCommitMessage method private --- src/main/java/com/limechain/grandpa/GrandpaService.java | 3 +-- .../java/com/limechain/grandpa/GrandpaServiceTest.java | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index eb81637b3..6b9a1a4dd 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -265,7 +265,6 @@ private Vote findBestPreVoteCandidate(GrandpaRound grandpaRound) { return currentVote; } - /** * Selects the block with the most votes from the provided map of blocks. * If multiple blocks have the same number of votes, it returns the one with the highest block number. @@ -485,7 +484,7 @@ private Vote getLastFinalizedBlockAsVote() { * 1. As the primary validator, broadcasting a commit message for the best candidate block of the previous round. * 2. During attempt-to-finalize, broadcasting a commit message for the best candidate block of the current round. */ - public void broadcastCommitMessage(GrandpaRound grandpaRound) { + private void broadcastCommitMessage(GrandpaRound grandpaRound) { Vote bestCandidate = findBestFinalCandidate(grandpaRound); PreCommit[] precommits = transformToCompactJustificationFormat(grandpaRound.getPreCommits()); diff --git a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java index 699c2d61f..7c7b47ee9 100644 --- a/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java +++ b/src/test/java/com/limechain/grandpa/GrandpaServiceTest.java @@ -791,7 +791,7 @@ void testSelectBlockWithMostVotesWhereLastFinalizedBlockIsWithGreaterBlockNumber } @Test - void testBroadcastCommitMessageWhenPrimaryValidator() { + void testBroadcastCommitMessageWhenPrimaryValidator() throws Exception { Hash256 authorityPublicKey = new Hash256(THREES_ARRAY); Map signedVotes = new HashMap<>(); Vote vote = new Vote(new Hash256(ONES_ARRAY), BigInteger.valueOf(123L)); @@ -807,7 +807,10 @@ void testBroadcastCommitMessageWhenPrimaryValidator() { when(blockState.getHighestFinalizedHeader()).thenReturn(blockHeader); when(grandpaSetState.getSetId()).thenReturn(BigInteger.valueOf(42L)); - grandpaService.broadcastCommitMessage(previousRound); + Method method = GrandpaService.class.getDeclaredMethod("broadcastCommitMessage", GrandpaRound.class); + method.setAccessible(true); + + method.invoke(grandpaService, previousRound); ArgumentCaptor commitMessageCaptor = ArgumentCaptor.forClass(CommitMessage.class); verify(peerMessageCoordinator).sendCommitMessageToPeers(commitMessageCaptor.capture()); From 5b2124b0c29416bb085051646a7b1dd4bd7aea7a Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 16:30:02 +0200 Subject: [PATCH 14/15] feat: add commit message list to grandpaRound --- .../com/limechain/grandpa/GrandpaService.java | 2 +- .../limechain/grandpa/state/GrandpaRound.java | 14 +++++++++++++ .../grandpa/state/GrandpaSetState.java | 20 ------------------- .../sync/warpsync/WarpSyncState.java | 4 +++- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/GrandpaService.java b/src/main/java/com/limechain/grandpa/GrandpaService.java index 6b9a1a4dd..fe4c7b3f8 100644 --- a/src/main/java/com/limechain/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/grandpa/GrandpaService.java @@ -55,7 +55,7 @@ private void attemptToFinalizeAt(GrandpaRound grandpaRound) { BlockHeader header = blockState.getHeader(bestFinalCandidate.getBlockHash()); blockState.setFinalizedHash(header, grandpaRound.getRoundNumber(), grandpaSetState.getSetId()); - if (!grandpaSetState.isCommitMessageInArchive(bestFinalCandidate)) { + if (!grandpaRound.isCommitMessageInArchive(bestFinalCandidate)) { broadcastCommitMessage(grandpaRound); } } diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaRound.java b/src/main/java/com/limechain/grandpa/state/GrandpaRound.java index 4f3711a87..7b440df45 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaRound.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaRound.java @@ -2,6 +2,7 @@ import com.limechain.exception.grandpa.GrandpaGenericException; import com.limechain.network.protocol.grandpa.messages.catchup.res.SignedVote; +import com.limechain.network.protocol.grandpa.messages.commit.CommitMessage; import com.limechain.network.protocol.grandpa.messages.commit.Vote; import io.emeraldpay.polkaj.types.Hash256; import lombok.Getter; @@ -9,6 +10,8 @@ import java.io.Serializable; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +33,8 @@ public class GrandpaRound implements Serializable { private Map> pvEquivocations = new ConcurrentHashMap<>(); private Map> pcEquivocations = new ConcurrentHashMap<>(); + private List commitMessagesArchive = new ArrayList<>(); + public Vote getPreVotedBlock() { if (preVotedBlock == null) throw new GrandpaGenericException("Pre-voted block has not been set"); return preVotedBlock; @@ -51,4 +56,13 @@ public long getPcEquivocationsCount() { .mapToInt(Set::size) .sum(); } + + public void addCommitMessageToArchive(CommitMessage message) { + commitMessagesArchive.add(message); + } + + public boolean isCommitMessageInArchive(Vote vote) { + return commitMessagesArchive.stream() + .anyMatch(cm -> cm.getVote().equals(vote)); + } } diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java index 0a0cb43d5..52b16e003 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java @@ -56,7 +56,6 @@ public class GrandpaSetState { private final KeyStore keyStore; private final KVRepository repository; - private final Map> commitMessagesArchive = new HashMap<>(); private final PriorityQueue authoritySetChanges = new PriorityQueue<>(AuthoritySetChange.getComparator()); @@ -153,8 +152,6 @@ public void startNewSet(List authorities) { roundCache.addRound(setId, grandpaRound); this.authorities = authorities; - cleanCommitMessagesArchive(); - log.log(Level.INFO, "Successfully transitioned to authority set id: " + setId); } @@ -242,21 +239,4 @@ public boolean participatesAsVoter() { return authorities.stream() .anyMatch(a -> keyStore.getKeyPair(KeyType.GRANDPA, a.getPublicKey()).isPresent()); } - - public void addCommitMessageToArchive(CommitMessage message) { - commitMessagesArchive.putIfAbsent(setId, new ArrayList<>()); - List commitMessages = commitMessagesArchive.get(setId); - commitMessages.add(message); - } - - // Removes commit messages two SetIds behind the current one, - // keeping only those received in the current and previous set. - public void cleanCommitMessagesArchive() { - commitMessagesArchive.remove(setId.subtract(BigInteger.TWO)); - } - - public boolean isCommitMessageInArchive(Vote vote) { - return commitMessagesArchive.get(setId).stream() - .anyMatch(cm -> cm.getVote().equals(vote)); - } } \ No newline at end of file diff --git a/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java b/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java index a29d221b8..69d36ae1c 100644 --- a/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java +++ b/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java @@ -143,7 +143,9 @@ public synchronized void syncCommit(CommitMessage commitMessage, PeerId peerId) return; } - grandpaSetState.addCommitMessageToArchive(commitMessage); + grandpaSetState.getRoundCache() + .getRound(commitMessage.getSetId(), commitMessage.getRoundNumber()) + .addCommitMessageToArchive(commitMessage); if (warpSyncFinished && !grandpaSetState.participatesAsVoter()) { updateState(commitMessage); From ff10f949d45d70c803e68bae26f7747a6e9c1eaf Mon Sep 17 00:00:00 2001 From: Grigorov-Georgi Date: Thu, 23 Jan 2025 16:35:25 +0200 Subject: [PATCH 15/15] chore: remove unused imports --- src/main/java/com/limechain/grandpa/state/GrandpaSetState.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java index 52b16e003..ef787acea 100644 --- a/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java +++ b/src/main/java/com/limechain/grandpa/state/GrandpaSetState.java @@ -4,7 +4,6 @@ import com.limechain.chain.lightsyncstate.LightSyncState; import com.limechain.exception.grandpa.GrandpaGenericException; import com.limechain.network.protocol.grandpa.messages.catchup.res.SignedVote; -import com.limechain.network.protocol.grandpa.messages.commit.CommitMessage; import com.limechain.network.protocol.grandpa.messages.commit.Vote; import com.limechain.network.protocol.grandpa.messages.consensus.GrandpaConsensusMessage; import com.limechain.network.protocol.grandpa.messages.vote.SignedMessage; @@ -26,10 +25,8 @@ import lombok.extern.java.Log; import java.math.BigInteger; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PriorityQueue;