diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyStore.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyStore.java index 9398dc66435..9e07bf30e31 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyStore.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/forkchoice/ReadOnlyStore.java @@ -140,4 +140,7 @@ SafeFuture> retrieveCheckpointState( // implements is_parent_strong from fork-choice Consensus Spec SafeFuture> isParentStrong(final Bytes32 parentRoot); + + // implements is_ffg_competitive from Consensus Spec + Optional isFfgCompetitive(final Bytes32 headRoot, final Bytes32 parentRoot); } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java index 0d19940e134..574e0149882 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/datastructures/forkchoice/TestStoreImpl.java @@ -256,6 +256,11 @@ public SafeFuture> isParentStrong(Bytes32 parentRoot) { return SafeFuture.completedFuture(Optional.empty()); } + @Override + public Optional isFfgCompetitive(Bytes32 headRoot, Bytes32 parentRoot) { + return Optional.empty(); + } + @Override public Optional> getBlobSidecarsIfAvailable( final SlotAndBlockRoot slotAndBlockRoot) { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java b/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java index bae852b2a8d..d7bc45adb74 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/Store.java @@ -625,6 +625,25 @@ public SafeFuture> isParentStrong(final Bytes32 parentRoot) { }); } + @Override + public Optional isFfgCompetitive(Bytes32 headRoot, Bytes32 parentRoot) { + final Optional maybeHeadData = getBlockDataFromForkChoiceStrategy(headRoot); + final Optional maybeParentData = getBlockDataFromForkChoiceStrategy(parentRoot); + if (maybeParentData.isEmpty() || maybeHeadData.isEmpty()) { + return Optional.empty(); + } + final Checkpoint headUnrealizedJustifiedCheckpoint = + maybeHeadData.get().getCheckpoints().getUnrealizedJustifiedCheckpoint(); + final Checkpoint parentUnrealizedJustifiedCheckpoint = + maybeParentData.get().getCheckpoints().getUnrealizedJustifiedCheckpoint(); + LOG.debug( + "head {}, compared to parent {}", + headUnrealizedJustifiedCheckpoint, + parentUnrealizedJustifiedCheckpoint); + return Optional.of( + headUnrealizedJustifiedCheckpoint.equals(parentUnrealizedJustifiedCheckpoint)); + } + private Optional getBlockDataFromForkChoiceStrategy(final Bytes32 root) { readLock.lock(); try { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransaction.java b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransaction.java index db1b031a65f..595e502b1f5 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransaction.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransaction.java @@ -461,6 +461,11 @@ public SafeFuture> isParentStrong(Bytes32 parentRoot) { return store.isParentStrong(parentRoot); } + @Override + public Optional isFfgCompetitive(Bytes32 headRoot, Bytes32 parentRoot) { + return store.isFfgCompetitive(headRoot, parentRoot); + } + @Override public Optional getBlockIfAvailable(final Bytes32 blockRoot) { return Optional.ofNullable(blockData.get(blockRoot)) diff --git a/storage/src/test/java/tech/pegasys/teku/storage/store/StoreTest.java b/storage/src/test/java/tech/pegasys/teku/storage/store/StoreTest.java index 4c421639af0..3e2170c0458 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/store/StoreTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/store/StoreTest.java @@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.assertThatSafeFuture; import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.safeJoin; @@ -33,6 +34,7 @@ import tech.pegasys.teku.infrastructure.metrics.StubMetricsSystem; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; @@ -182,6 +184,79 @@ public void isParentStrong_wityZeroWeight() { }); } + @Test + public void isFfgCompetitive_checkpointMatches() { + final BlockCheckpoints headBlockCheckpoint = mock(BlockCheckpoints.class); + final BlockCheckpoints parentBlockCheckpoint = mock(BlockCheckpoints.class); + final Checkpoint checkpoint = new Checkpoint(UInt64.ZERO, Bytes32.random()); + when(headBlockCheckpoint.getUnrealizedJustifiedCheckpoint()).thenReturn(checkpoint); + when(parentBlockCheckpoint.getUnrealizedJustifiedCheckpoint()).thenReturn(checkpoint); + processChainHeadWithMockForkChoiceStrategy( + (store, blockAndState) -> { + final Bytes32 root = blockAndState.getRoot(); + final Bytes32 parentRoot = blockAndState.getBlock().getParentRoot(); + setProtoNodeDataForBlock(blockAndState, headBlockCheckpoint, parentBlockCheckpoint); + assertThat(store.isFfgCompetitive(root, parentRoot)).contains(true); + }); + } + + @Test + public void isFfgCompetitive_checkpointDifferent() { + final BlockCheckpoints headBlockCheckpoint = mock(BlockCheckpoints.class); + final BlockCheckpoints parentBlockCheckpoint = mock(BlockCheckpoints.class); + final Checkpoint checkpoint = new Checkpoint(UInt64.ZERO, Bytes32.random()); + final Checkpoint checkpointParent = new Checkpoint(UInt64.ONE, Bytes32.random()); + when(headBlockCheckpoint.getUnrealizedJustifiedCheckpoint()).thenReturn(checkpoint); + when(parentBlockCheckpoint.getUnrealizedJustifiedCheckpoint()).thenReturn(checkpointParent); + processChainHeadWithMockForkChoiceStrategy( + (store, blockAndState) -> { + final Bytes32 root = blockAndState.getRoot(); + final Bytes32 parentRoot = blockAndState.getBlock().getParentRoot(); + setProtoNodeDataForBlock(blockAndState, headBlockCheckpoint, parentBlockCheckpoint); + assertThat(store.isFfgCompetitive(root, parentRoot)).contains(false); + }); + } + + @Test + public void isFfgCompetitive_missingProtoNodeEntries() { + processChainHeadWithMockForkChoiceStrategy( + (store, blockAndState) -> { + final Bytes32 root = blockAndState.getRoot(); + final Bytes32 parentRoot = blockAndState.getBlock().getParentRoot(); + assertThat(store.isFfgCompetitive(root, parentRoot)).isEmpty(); + }); + } + + private void setProtoNodeDataForBlock( + SignedBlockAndState blockAndState, + BlockCheckpoints headCheckpoint, + BlockCheckpoints parentCheckpoint) { + final Bytes32 root = blockAndState.getRoot(); + final Bytes32 parentRoot = blockAndState.getParentRoot(); + final ProtoNodeData protoNodeData = + new ProtoNodeData( + UInt64.ONE, + root, + blockAndState.getParentRoot(), + blockAndState.getStateRoot(), + Bytes32.random(), + ProtoNodeValidationStatus.VALID, + headCheckpoint, + UInt64.ZERO); + final ProtoNodeData parentNodeData = + new ProtoNodeData( + UInt64.ZERO, + parentRoot, + Bytes32.random(), + blockAndState.getStateRoot(), + Bytes32.random(), + ProtoNodeValidationStatus.VALID, + parentCheckpoint, + UInt64.ZERO); + when(dummyForkChoiceStrategy.getBlockData(root)).thenReturn(Optional.of(protoNodeData)); + when(dummyForkChoiceStrategy.getBlockData(parentRoot)).thenReturn(Optional.of(parentNodeData)); + } + private void setProtoNodeDataForBlock( SignedBlockAndState blockAndState, final UInt64 headValue, final UInt64 parentValue) { final Bytes32 root = blockAndState.getRoot();