Skip to content

Commit

Permalink
add payload attestation aggregation (#8599)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-aouadi authored and StefanBratanov committed Oct 2, 2024
1 parent 3217d4c commit 7c57050
Show file tree
Hide file tree
Showing 17 changed files with 484 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@
public class BlockOperationSelectorFactory {
private final Spec spec;
private final AggregatingAttestationPool attestationPool;
private final PayloadAttestationPool payloadAttestationPool;
private final OperationPool<AttesterSlashing> attesterSlashingPool;
private final OperationPool<ProposerSlashing> proposerSlashingPool;
private final OperationPool<SignedVoluntaryExit> voluntaryExitPool;
private final OperationPool<SignedBlsToExecutionChange> blsToExecutionChangePool;
private final SyncCommitteeContributionPool contributionPool;
private final ExecutionPayloadHeaderPool executionPayloadHeaderPool;
private final PayloadAttestationPool payloadAttestationPool;
private final DepositProvider depositProvider;
private final Eth1DataCache eth1DataCache;
private final GraffitiBuilder graffitiBuilder;
Expand All @@ -109,13 +109,13 @@ public BlockOperationSelectorFactory(
final ExecutionLayerBlockProductionManager executionLayerBlockProductionManager) {
this.spec = spec;
this.attestationPool = attestationPool;
this.payloadAttestationPool = payloadAttestationPool;
this.attesterSlashingPool = attesterSlashingPool;
this.proposerSlashingPool = proposerSlashingPool;
this.voluntaryExitPool = voluntaryExitPool;
this.blsToExecutionChangePool = blsToExecutionChangePool;
this.contributionPool = contributionPool;
this.executionPayloadHeaderPool = executionPayloadHeaderPool;
this.payloadAttestationPool = payloadAttestationPool;
this.depositProvider = depositProvider;
this.eth1DataCache = eth1DataCache;
this.graffitiBuilder = graffitiBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public abstract class AbstractBlockFactoryTest {

protected final AggregatingAttestationPool attestationsPool =
mock(AggregatingAttestationPool.class);
protected final PayloadAttestationPool payloadAttestationPool =
mock(PayloadAttestationPool.class);
protected final OperationPool<AttesterSlashing> attesterSlashingPool = mock(OperationPool.class);
protected final OperationPool<ProposerSlashing> proposerSlashingPool = mock(OperationPool.class);
protected final OperationPool<SignedVoluntaryExit> voluntaryExitPool = mock(OperationPool.class);
Expand All @@ -113,8 +115,6 @@ public abstract class AbstractBlockFactoryTest {
mock(SyncCommitteeContributionPool.class);
protected final ExecutionPayloadHeaderPool executionPayloadHeaderPool =
mock(ExecutionPayloadHeaderPool.class);
protected final PayloadAttestationPool payloadAttestationPool =
mock(PayloadAttestationPool.class);
protected final DepositProvider depositProvider = mock(DepositProvider.class);
protected final Eth1DataCache eth1DataCache = mock(Eth1DataCache.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class BlockOperationSelectorFactoryTest {
mock(OperationValidator.class);

private final AggregatingAttestationPool attestationPool = mock(AggregatingAttestationPool.class);
private final PayloadAttestationPool payloadAttestationPool = mock(PayloadAttestationPool.class);
private final OperationPool<AttesterSlashing> attesterSlashingPool =
new SimpleOperationPool<>(
"attester_slashing",
Expand Down Expand Up @@ -172,9 +173,6 @@ class BlockOperationSelectorFactoryTest {
private final ExecutionPayloadHeaderPool executionPayloadHeaderPool =
new ExecutionPayloadHeaderPool(executionPayloadHeaderValidator);

private final PayloadAttestationPool payloadAttestationPool =
new PayloadAttestationPool(spec, metricsSystem);

private final DepositProvider depositProvider = mock(DepositProvider.class);
private final Eth1DataCache eth1DataCache = mock(Eth1DataCache.class);
private final Bytes32 defaultGraffiti = dataStructureUtil.randomBytes32();
Expand Down
4 changes: 2 additions & 2 deletions ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java
Original file line number Diff line number Diff line change
Expand Up @@ -923,9 +923,9 @@ public Optional<CommitteeAssignment> getCommitteeAssignment(
return atEpoch(epoch).getValidatorsUtil().getCommitteeAssignment(state, epoch, validatorIndex);
}

public Optional<CommitteeAssignment> getPtcAssignment(
public Optional<UInt64> getPtcAssignment(
final BeaconState state, final UInt64 epoch, final int validatorIndex) {
return atEpoch(epoch).getValidatorsUtil().getCommitteeAssignment(state, epoch, validatorIndex);
return atEpoch(epoch).getValidatorsUtil().getPtcAssignment(state, epoch, validatorIndex);
}

public Int2ObjectMap<CommitteeAssignment> getValidatorIndexToCommitteeAssignmentMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ public PayloadAttestation createFromBackingNode(final TreeNode node) {
public SszBitvectorSchema<?> getAggregationBitsSchema() {
return (SszBitvectorSchema<?>) getFieldSchema0();
}

public SszBitvector createEmptyAggregationBits() {
final SszBitvectorSchema<?> bitvectorSchema = getAggregationBitsSchema();
return bitvectorSchema.ofBits(Math.toIntExact(bitvectorSchema.getMaxLength()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,33 @@ public Int2ObjectMap<CommitteeAssignment> getValidatorIndexToCommitteeAssignment
return assignmentMap;
}

/**
* Return the slot during the requested epoch in which the validator with index `validator_index`
* is a member of the PTC. Returns None if no assignment is found.
*
* @param state the BeaconState.
* @param epoch either on or between previous or current epoch.
* @param validatorIndex the validator that is calling this function.
* @return Optional containing the slot if any, empty otherwise
*/
public Optional<UInt64> getPtcAssignment(
final BeaconState state, final UInt64 epoch, final int validatorIndex) {
final UInt64 nextEpoch = beaconStateAccessors.getCurrentEpoch(state).plus(UInt64.ONE);
checkArgument(epoch.compareTo(nextEpoch) <= 0, "get_ptc_assignment: Epoch number too high");

final UInt64 startSlot = miscHelpers.computeStartSlotAtEpoch(epoch);
for (UInt64 slot = startSlot;
slot.isLessThan(startSlot.plus(specConfig.getSlotsPerEpoch()));
slot = slot.plus(UInt64.ONE)) {
final IntList ptcCommittee =
BeaconStateAccessorsEip7732.required(beaconStateAccessors).getPtc(state, slot);
if (ptcCommittee.contains(validatorIndex)) {
return Optional.of(slot);
}
}
return Optional.empty();
}

public Int2ObjectMap<UInt64> getValidatorIndexToPctAssignmentMap(
final BeaconState state, final UInt64 epoch) {
final Int2ObjectMap<UInt64> assignmentMap = new Int2ObjectArrayMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ private void removeAttestationsPriorToSlot(final UInt64 firstValidAttestationSlo
if (!dataHashesToRemove.isEmpty()) {
LOG.trace(
"firstValidAttestationSlot: {}, removing: {}",
() -> firstValidAttestationSlot,
dataHashesToRemove::size);
firstValidAttestationSlot.longValue(),
dataHashesToRemove.size());
}
dataHashesToRemove.clear();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright Consensys Software Inc., 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.statetransition.attestation;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation;
import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationData;
import tech.pegasys.teku.statetransition.attestation.utils.PayloadAttestationBitsAggregator;

public class MatchingDataPayloadAttestationGroup implements Iterable<PayloadAttestation> {

private final Spec spec;
private final PayloadAttestationData payloadAttestationData;
private final PayloadAttestationBitsAggregator payloadAttestationBitsAggregator;
private final NavigableMap<Integer, Set<PayloadAttestation>> payloadAttestationsByValidatorCount =
new TreeMap<>(Comparator.reverseOrder()); // Most validators first

public MatchingDataPayloadAttestationGroup(
final Spec spec, final PayloadAttestationData payloadAttestationData) {
this.spec = spec;
this.payloadAttestationData = payloadAttestationData;
this.payloadAttestationBitsAggregator = createEmptyPayloadAttestationBits();
}

private PayloadAttestationBitsAggregator createEmptyPayloadAttestationBits() {
return PayloadAttestationBitsAggregator.fromEmptyFromPayloadAttestationSchema(
spec.atSlot(payloadAttestationData.getSlot())
.getSchemaDefinitions()
.toVersionEip7732()
.orElseThrow()
.getPayloadAttestationSchema());
}

/**
* Adds a payload attestation message to this group. When possible, the payload attestation will
* be aggregated with others during iteration. Ignores payload attestations with no new, unseen
* aggregation bits.
*
* @param payloadAttestation the payload attestation message to add
* @return True if the attestation was added, false otherwise
*/
public boolean add(final PayloadAttestation payloadAttestation) {
if (payloadAttestationBitsAggregator.isSuperSetOf(payloadAttestation)) {
// All payload attestation bits have already been included on chain
return false;
}
return payloadAttestationsByValidatorCount
.computeIfAbsent(
payloadAttestation.getAggregationBits().getBitCount(), count -> new HashSet<>())
.add(payloadAttestation);
}

public PayloadAttestationData getPayloadAttestationData() {
return payloadAttestationData;
}

@Override
public Iterator<PayloadAttestation> iterator() {
return new AggregatingIterator();
}

public int size() {
return payloadAttestationsByValidatorCount.values().stream()
.map(Set::size)
.reduce(0, Integer::sum);
}

public Stream<PayloadAttestation> stream() {
return StreamSupport.stream(spliterator(), false);
}

@SuppressWarnings("unused")
private class AggregatingIterator implements Iterator<PayloadAttestation> {

private final PayloadAttestationBitsAggregator payloadAttestationBitsAggregator;

private Iterator<PayloadAttestation> remainingPayloadAttestations =
getRemainingPayloadAttestations();

private AggregatingIterator() {
payloadAttestationBitsAggregator =
MatchingDataPayloadAttestationGroup.this.payloadAttestationBitsAggregator.copy();
}

@Override
public boolean hasNext() {
if (!remainingPayloadAttestations.hasNext()) {
remainingPayloadAttestations = getRemainingPayloadAttestations();
}
return remainingPayloadAttestations.hasNext();
}

@Override
public PayloadAttestation next() {
final PayloadAttestationAggregateBuilder builder =
new PayloadAttestationAggregateBuilder(spec, payloadAttestationData);
remainingPayloadAttestations.forEachRemaining(
candidate -> {
if (builder.aggregate(candidate)) {
payloadAttestationBitsAggregator.or(candidate);
}
});
return builder.buildAggregate();
}

public Iterator<PayloadAttestation> getRemainingPayloadAttestations() {
return payloadAttestationsByValidatorCount.values().stream()
.flatMap(Set::stream)
.filter(candidate -> !payloadAttestationBitsAggregator.isSuperSetOf(candidate))
.iterator();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright Consensys Software Inc., 2024
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

package tech.pegasys.teku.statetransition.attestation;

import static com.google.common.base.Preconditions.checkState;

import java.util.HashSet;
import java.util.Set;
import tech.pegasys.teku.bls.BLS;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestation;
import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationData;
import tech.pegasys.teku.spec.datastructures.operations.PayloadAttestationSchema;
import tech.pegasys.teku.statetransition.attestation.utils.PayloadAttestationBitsAggregator;

public class PayloadAttestationAggregateBuilder {

private final Spec spec;
private final Set<PayloadAttestation> includedPayloadAttestations = new HashSet<>();
private final PayloadAttestationData payloadAttestationData;
private PayloadAttestationBitsAggregator payloadAttestationBitsAggregator;

PayloadAttestationAggregateBuilder(
final Spec spec, final PayloadAttestationData payloadAttestationData) {
this.spec = spec;
this.payloadAttestationData = payloadAttestationData;
}

public boolean aggregate(final PayloadAttestation payloadAttestation) {

if (payloadAttestationBitsAggregator == null) {
includedPayloadAttestations.add(payloadAttestation);
payloadAttestationBitsAggregator = PayloadAttestationBitsAggregator.of(payloadAttestation);
return true;
}
if (payloadAttestationBitsAggregator.aggregateWith(payloadAttestation)) {
includedPayloadAttestations.add(payloadAttestation);
return true;
}
return false;
}

public PayloadAttestation buildAggregate() {
checkState(
payloadAttestationBitsAggregator != null,
"Must aggregate at least one payload attestation");
final PayloadAttestationSchema payloadAttestationSchema =
spec.atSlot(payloadAttestationData.getSlot())
.getSchemaDefinitions()
.toVersionEip7732()
.orElseThrow()
.getPayloadAttestationSchema();
return payloadAttestationSchema.create(
payloadAttestationBitsAggregator.getAggregationBits(),
payloadAttestationData,
BLS.aggregate(
includedPayloadAttestations.stream().map(PayloadAttestation::getSignature).toList()));
}
}
Loading

0 comments on commit 7c57050

Please sign in to comment.