diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java index 7ea6bf77856..9fa42ea52c3 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java @@ -233,7 +233,9 @@ public DataProvider build() { isLivenessTrackingEnabled, activeValidatorChannel, proposersDataManager, - forkChoiceNotifier); + forkChoiceNotifier, + recentChainData, + spec); final ChainDataProvider chainDataProvider = new ChainDataProvider(spec, recentChainData, combinedChainDataClient, rewardCalculator); final SyncDataProvider syncDataProvider = diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java index cd33ea4cdd1..220807bb76d 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java @@ -26,7 +26,9 @@ import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ProcessedAttestationListener; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -47,6 +49,7 @@ import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; import tech.pegasys.teku.statetransition.validatorcache.ActiveValidatorChannel; +import tech.pegasys.teku.storage.client.RecentChainData; import tech.pegasys.teku.validator.api.SubmitDataError; public class NodeDataProvider { @@ -63,6 +66,8 @@ public class NodeDataProvider { private final boolean isLivenessTrackingEnabled; private final ProposersDataManager proposersDataManager; private final ForkChoiceNotifier forkChoiceNotifier; + private final RecentChainData recentChainData; + private final Spec spec; public NodeDataProvider( final AggregatingAttestationPool attestationPool, @@ -76,7 +81,9 @@ public NodeDataProvider( final boolean isLivenessTrackingEnabled, final ActiveValidatorChannel activeValidatorChannel, final ProposersDataManager proposersDataManager, - final ForkChoiceNotifier forkChoiceNotifier) { + final ForkChoiceNotifier forkChoiceNotifier, + final RecentChainData recentChainData, + final Spec spec) { this.attestationPool = attestationPool; this.attesterSlashingPool = attesterSlashingsPool; this.proposerSlashingPool = proposerSlashingPool; @@ -89,6 +96,8 @@ public NodeDataProvider( this.isLivenessTrackingEnabled = isLivenessTrackingEnabled; this.proposersDataManager = proposersDataManager; this.forkChoiceNotifier = forkChoiceNotifier; + this.recentChainData = recentChainData; + this.spec = spec; } public List getAttestations( @@ -96,6 +105,28 @@ public List getAttestations( return attestationPool.getAttestations(maybeSlot, maybeCommitteeIndex); } + public ObjectAndMetaData> getAttestationsAndMetaData( + final Optional maybeSlot, final Optional maybeCommitteeIndex) { + return lookupMetaData( + attestationPool.getAttestations(maybeSlot, maybeCommitteeIndex), maybeSlot); + } + + private ObjectAndMetaData> lookupMetaData( + final List attestations, final Optional maybeSlot) { + final UInt64 slot = getSlot(attestations, maybeSlot); + return new ObjectAndMetaData<>( + attestations, spec.atSlot(slot).getMilestone(), false, false, false); + } + + private UInt64 getSlot(final List attestations, final Optional maybeSlot) { + return maybeSlot.orElseGet( + () -> + attestations.stream() + .findFirst() + .map(attestation -> attestation.getData().getSlot()) + .orElseGet(() -> recentChainData.getCurrentSlot().orElse(UInt64.ZERO))); + } + public List getAttesterSlashings() { return new ArrayList<>(attesterSlashingPool.getAll()); } diff --git a/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java b/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java index a43ee09a500..051cfb5617c 100644 --- a/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java +++ b/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java @@ -15,16 +15,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -40,6 +46,7 @@ import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; import tech.pegasys.teku.statetransition.validatorcache.ActiveValidatorChannel; +import tech.pegasys.teku.storage.client.RecentChainData; import tech.pegasys.teku.validator.api.SubmitDataError; @SuppressWarnings("unchecked") @@ -53,6 +60,7 @@ public class NodeDataProviderTest { private final ActiveValidatorChannel validatorChannel = mock(ActiveValidatorChannel.class); private final ProposersDataManager proposersDataManager = mock(ProposersDataManager.class); private final ForkChoiceNotifier forkChoiceNotifier = mock(ForkChoiceNotifier.class); + private final RecentChainData recentChainData = mock(RecentChainData.class); private final OperationPool attesterSlashingPool = mock(OperationPool.class); @@ -82,7 +90,9 @@ public void setup() { false, validatorChannel, proposersDataManager, - forkChoiceNotifier); + forkChoiceNotifier, + recentChainData, + spec); } @Test @@ -113,4 +123,70 @@ void blsToExecutionChanges_ReturnsListOfErrors() throws ExecutionException, Inte assertThat(future.get()) .isEqualTo(List.of(new SubmitDataError(UInt64.ONE, "Computer says no"))); } + + @Test + void attestationsMetaDataLookUp_UseFirstAttestationSlot_WhenSlotParamNotProvided() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())) + .thenReturn( + List.of( + dataStructureUtil.randomAttestation(5), dataStructureUtil.randomAttestation(10))); + provider.getAttestationsAndMetaData(Optional.empty(), Optional.empty()); + verify(specMock).atSlot(eq(UInt64.valueOf(5))); + } + + @Test + void attestationsMetaDataLookUp_UseSlot_WhenSlotParamProvided() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())) + .thenReturn( + List.of( + dataStructureUtil.randomAttestation(5), dataStructureUtil.randomAttestation(10))); + provider.getAttestationsAndMetaData(Optional.of(UInt64.valueOf(8)), Optional.empty()); + verify(specMock).atSlot(eq(UInt64.valueOf(8))); + } + + @Test + void attestationsMetaDataLookUp_UseCurrentSlot_WhenSlotParamNotProvided_EmptyList() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())).thenReturn(Collections.emptyList()); + final UInt64 currentSlot = UInt64.valueOf(8); + when(recentChainData.getCurrentSlot()).thenReturn(Optional.of(currentSlot)); + provider.getAttestationsAndMetaData(Optional.empty(), Optional.empty()); + verify(specMock).atSlot(eq(currentSlot)); + } + + @Test + void attestationsMetaDataLookUp_UseSlotZero_WhenSlotParamNotProvided_EmptyList_NoCurrentSlot() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())).thenReturn(Collections.emptyList()); + when(recentChainData.getCurrentSlot()).thenReturn(Optional.empty()); + provider.getAttestationsAndMetaData(Optional.empty(), Optional.empty()); + verify(specMock).atSlot(eq(UInt64.ZERO)); + } + + private Spec setUpMockedSpec() { + final Spec specMock = mock(Spec.class); + final SpecVersion specVersionMock = mock(SpecVersion.class); + final SpecMilestone specMilestone = mock(SpecMilestone.class); + when(specVersionMock.getMilestone()).thenReturn(specMilestone); + when(specMock.atSlot(any())).thenReturn(specVersionMock); + provider = + new NodeDataProvider( + attestationPool, + attesterSlashingPool, + proposerSlashingPool, + voluntaryExitPool, + blsToExecutionChangePool, + syncCommitteeContributionPool, + blockBlobSidecarsTrackersPool, + attestationManager, + false, + validatorChannel, + proposersDataManager, + forkChoiceNotifier, + recentChainData, + specMock); + return specMock; + } }