diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index 04648dd04fb..91515842187 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -586,13 +586,20 @@ public SafeFuture> sendSignedAttestations( } private SafeFuture processAttestation(final Attestation attestation) { + final ValidatableAttestation validatableAttestation = + ValidatableAttestation.fromValidator(spec, attestation); return attestationManager - .addAttestation(ValidatableAttestation.fromValidator(spec, attestation), Optional.empty()) + .addAttestation(validatableAttestation, Optional.empty()) .thenPeek( result -> { if (!result.isReject()) { - dutyMetrics.onAttestationPublished(attestation.getData().getSlot()); - performanceTracker.saveProducedAttestation(attestation); + // When saving the attestation in performance tracker, we want to make sure we save + // the converted attestation. + // The conversion happens during processing and is saved in the validatable + // attestation. + final Attestation convertedAttestation = validatableAttestation.getAttestation(); + dutyMetrics.onAttestationPublished(convertedAttestation.getData().getSlot()); + performanceTracker.saveProducedAttestation(convertedAttestation); } else { VALIDATOR_LOGGER.producedInvalidAttestation( attestation.getData().getSlot(), diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java index 381229ffca5..8031f4f0a30 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.validator.coordinator.performance; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; import it.unimi.dsi.fastutil.ints.Int2IntMap; @@ -424,6 +425,7 @@ private SafeFuture>> getAttestationsIncludedInEpoc @Override public void saveProducedAttestation(final Attestation attestation) { + checkState(!attestation.isSingleAttestation(), "Single attestation is not supported"); final UInt64 epoch = spec.computeEpochAtSlot(attestation.getData().getSlot()); final Set attestationsInEpoch = producedAttestationsByEpoch.computeIfAbsent(epoch, __ -> concurrentSet()); diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index d882571c41d..770ecbad932 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -819,6 +819,53 @@ public void sendSignedAttestations_shouldAddAttestationToAttestationManager() { .addAttestation(ValidatableAttestation.from(spec, attestation), Optional.empty()); } + @Test + void sendSignedAttestations_shouldSaveConvertedAttestationFromSingleAttestation() { + spec = TestSpecFactory.createMinimalElectra(); + dataStructureUtil = new DataStructureUtil(spec); + validatorApiHandler = + new ValidatorApiHandler( + chainDataProvider, + nodeDataProvider, + networkDataProvider, + chainDataClient, + syncStateProvider, + blockFactory, + attestationPool, + attestationManager, + attestationTopicSubscriptions, + activeValidatorTracker, + dutyMetrics, + performanceTracker, + spec, + forkChoiceTrigger, + proposersDataManager, + syncCommitteeMessagePool, + syncCommitteeContributionPool, + syncCommitteeSubscriptionManager, + blockProductionPerformanceFactory, + blockPublisher); + + final Attestation attestation = dataStructureUtil.randomSingleAttestation(); + final Attestation convertedAttestation = dataStructureUtil.randomAttestation(); + doAnswer( + invocation -> { + invocation + .getArgument(0, ValidatableAttestation.class) + .convertToAggregatedFormatFromSingleAttestation(convertedAttestation); + return completedFuture(InternalValidationResult.ACCEPT); + }) + .when(attestationManager) + .addAttestation(any(ValidatableAttestation.class), any()); + + final SafeFuture> result = + validatorApiHandler.sendSignedAttestations(List.of(attestation)); + assertThat(result).isCompletedWithValue(emptyList()); + + verify(dutyMetrics).onAttestationPublished(convertedAttestation.getData().getSlot()); + verify(performanceTracker).saveProducedAttestation(convertedAttestation); + } + @Test void sendSignedAttestations_shouldAddToDutyMetricsAndPerformanceTrackerWhenNotInvalid() { final Attestation attestation = dataStructureUtil.randomAttestation(); diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java index 3651eae033d..df11615cc16 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; -import java.util.List; import java.util.Optional; import tech.pegasys.teku.ethtests.finder.TestDefinition; import tech.pegasys.teku.infrastructure.ssz.SszData; @@ -68,9 +67,6 @@ public class OperationsTestExecutor implements TestExecutor { public static final String EXPECTED_STATE_FILE = "post.ssz_snappy"; - // TODO remove https://github.com/Consensys/teku/issues/8892 - private static final List IGNORED_TEST = List.of("invalid_nonset_bits_for_one_committee"); - private enum Operation { ATTESTER_SLASHING, PROPOSER_SLASHING, @@ -148,11 +144,6 @@ public OperationsTestExecutor(final String dataFileName, final Operation operati @Override public void runTest(final TestDefinition testDefinition) throws Exception { - // TODO remove https://github.com/Consensys/teku/issues/8892 - if (IGNORED_TEST.contains(testDefinition.getTestName())) { - return; - } - final BeaconState preState = loadStateFromSsz(testDefinition, "pre.ssz_snappy"); final DefaultOperationProcessor standardProcessor = diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java index 9bfe1da33b4..8693f37efc0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; @@ -725,16 +726,25 @@ private Optional checkCommittees( final BeaconState state, final UInt64 slot, final SszBitlist aggregationBits) { - int participantsCount = 0; + int committeeOffset = 0; for (final UInt64 committeeIndex : committeeIndices) { if (committeeIndex.isGreaterThanOrEqualTo(committeeCountPerSlot)) { return Optional.of(AttestationInvalidReason.COMMITTEE_INDEX_TOO_HIGH); } final IntList committee = beaconStateAccessorsElectra.getBeaconCommittee(state, slot, committeeIndex); - participantsCount += committee.size(); + final int currentCommitteeOffset = committeeOffset; + final boolean committeeHasAtLeastOneAttester = + IntStream.range(0, committee.size()) + .anyMatch( + committeeParticipantIndex -> + aggregationBits.isSet(currentCommitteeOffset + committeeParticipantIndex)); + if (!committeeHasAtLeastOneAttester) { + return Optional.of(AttestationInvalidReason.PARTICIPANTS_COUNT_MISMATCH); + } + committeeOffset += committee.size(); } - if (participantsCount != aggregationBits.size()) { + if (committeeOffset != aggregationBits.size()) { return Optional.of(AttestationInvalidReason.PARTICIPANTS_COUNT_MISMATCH); } return Optional.empty();