Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions beacon-chain/sync/data_column_sidecars.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sync
import (
"bytes"
"context"
"fmt"
"maps"
"slices"
"sync"
Expand Down Expand Up @@ -243,8 +244,10 @@ func requestDirectSidecarsFromPeers(
}

// Compute missing indices by root, excluding those already in storage.
var lastRoot [fieldparams.RootLength]byte
missingIndicesByRoot := make(map[[fieldparams.RootLength]byte]map[uint64]bool, len(incompleteRoots))
for root := range incompleteRoots {
lastRoot = root
storedIndices := storedIndicesByRoot[root]

missingIndices := make(map[uint64]bool, len(requestedIndices))
Expand All @@ -259,6 +262,7 @@ func requestDirectSidecarsFromPeers(
}
}

initialMissingRootCount := len(missingIndicesByRoot)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is unrelated with the PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. That's the reason why it's modified in a totally separated commit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we take it out of the PR?
id at least mention it in the github description, as reviewer, it was confusing to read at first

initialMissingCount := computeTotalCount(missingIndicesByRoot)

indicesByRootByPeer, err := computeIndicesByRootByPeer(params.P2P, slotByRoot, missingIndicesByRoot, connectedPeers)
Expand Down Expand Up @@ -301,11 +305,19 @@ func requestDirectSidecarsFromPeers(
}
}

log.WithFields(logrus.Fields{
"duration": time.Since(start),
"initialMissingCount": initialMissingCount,
"finalMissingCount": computeTotalCount(missingIndicesByRoot),
}).Debug("Requested direct data column sidecars from peers")
log := log.WithFields(logrus.Fields{
"duration": time.Since(start),
"initialMissingRootCount": initialMissingRootCount,
"initialMissingCount": initialMissingCount,
"finalMissingRootCount": len(missingIndicesByRoot),
"finalMissingCount": computeTotalCount(missingIndicesByRoot),
})

if initialMissingRootCount == 1 {
log = log.WithField("root", fmt.Sprintf("%#x", lastRoot))
}

log.Debug("Requested direct data column sidecars from peers")

return verifiedColumnsByRoot, nil
}
Expand Down
38 changes: 21 additions & 17 deletions beacon-chain/sync/pending_attestations_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package sync
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"slices"
"time"

"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
Expand All @@ -21,13 +21,12 @@ import (
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/time"
"github.com/OffchainLabs/prysm/v7/time/slots"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/sirupsen/logrus"
)

var pendingAttsLimit = 32768
const pendingAttsLimit = 32768

// This method processes pending attestations as a "known" block as arrived. With validations,
// the valid attestations get saved into the operation mem pool, and the invalid attestations gets deleted
Expand All @@ -50,16 +49,7 @@ func (s *Service) processPendingAttsForBlock(ctx context.Context, bRoot [32]byte
attestations := s.blkRootToPendingAtts[bRoot]
s.pendingAttsLock.RUnlock()

if len(attestations) > 0 {
start := time.Now()
s.processAttestations(ctx, attestations)
duration := time.Since(start)
log.WithFields(logrus.Fields{
"blockRoot": hex.EncodeToString(bytesutil.Trunc(bRoot[:])),
"pendingAttsCount": len(attestations),
"duration": duration,
}).Debug("Verified and saved pending attestations to pool")
}
s.processAttestations(ctx, attestations, bRoot)

randGen := rand.NewGenerator()
// Delete the missing block root key from pending attestation queue so a node will not request for the block again.
Expand All @@ -79,26 +69,43 @@ func (s *Service) processPendingAttsForBlock(ctx context.Context, bRoot [32]byte
return s.sendBatchRootRequest(ctx, pendingRoots, randGen)
}

func (s *Service) processAttestations(ctx context.Context, attestations []any) {
func (s *Service) processAttestations(ctx context.Context, attestations []any, blockRoot [32]byte) {
if len(attestations) == 0 {
return
}

startAggregate := time.Now()
atts := make([]ethpb.Att, 0, len(attestations))
aggregateAttAndProofCount := 0
for _, att := range attestations {
switch v := att.(type) {
case ethpb.Att:
atts = append(atts, v)
case ethpb.SignedAggregateAttAndProof:
s.processAggregate(ctx, v)
aggregateAttAndProofCount++
default:
log.Warnf("Unexpected attestation type %T, skipping", v)
}
}
durationAggregateAttAndProof := time.Since(startAggregate)

startAtts := time.Now()
for _, bucket := range bucketAttestationsByData(atts) {
s.processAttestationBucket(ctx, bucket)
}

durationAtts := time.Since(startAtts)

log.WithFields(logrus.Fields{
"blockRoot": fmt.Sprintf("%#x", blockRoot),
"pendingTotalCount": len(attestations),
"pendingAggregateAttAndProofCount": aggregateAttAndProofCount,
"pendingAttCount": len(atts),
"durationTotal": durationAggregateAttAndProof + durationAtts,
"durationAggregateAttAndProof": durationAggregateAttAndProof,
"durationAtts": durationAtts,
}).Debug("Verified and saved pending attestations to pool")
}

// attestationBucket groups attestations with the same AttestationData for batch processing.
Expand Down Expand Up @@ -395,9 +402,6 @@ func pendingAggregatesAreEqual(a, b ethpb.SignedAggregateAttAndProof) bool {
if a.Version() != b.Version() {
return false
}
if a.AggregateAttestationAndProof().GetAggregatorIndex() != b.AggregateAttestationAndProof().GetAggregatorIndex() {
return false
}
Comment on lines -398 to -400

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that one of the aggregates is valid if and only iff all other aggregates are valid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving to draft until your comment is solved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this PR, there is a flaw:
If the first aggregated attestation (for a given slot, committee index and aggregation bits) is received, but with an aggregator index which is not supposed to aggregate for this slot, then this aggregated attestation is inserted into the queue, and all other equivalent aggregated attestations are discarded.

Later, when processing the queue, we finally reject this aggregated attestation because it is not valid.
As a consequence, 0 aggregated attestation of this kind is neither accepted nor broadcasted.
==> This is a bug.

9969761 fixes is issue, by moving all verifications not needing the block (including the aggregator index) before inserting the aggregated attestation into the queue.
Only the verifications needing the block are done when pulling aggregated attestations from the queue.

As a bonus, it considerably reduces the process time when pulling aggregated attestations from the queue.

aAtt := a.AggregateAttestationAndProof().AggregateVal()
bAtt := b.AggregateAttestationAndProof().AggregateVal()
if aAtt.GetData().Slot != bAtt.GetData().Slot {
Expand Down
7 changes: 1 addition & 6 deletions beacon-chain/sync/pending_attestations_queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestProcessPendingAtts_NoBlockRequestBlock(t *testing.T) {
// Process block A (which exists and has no pending attestations)
// This should skip processing attestations for A and request blocks B and C
require.NoError(t, r.processPendingAttsForBlock(t.Context(), rootA))
require.LogsContain(t, hook, "Requesting block by root")
require.LogsContain(t, hook, "Requesting blocks by root")
}

func TestProcessPendingAtts_HasBlockSaveUnaggregatedAtt(t *testing.T) {
Expand Down Expand Up @@ -918,11 +918,6 @@ func Test_pendingAggregatesAreEqual(t *testing.T) {
b := &ethpb.SignedAggregateAttestationAndProofElectra{Message: &ethpb.AggregateAttestationAndProofElectra{AggregatorIndex: 1}}
assert.Equal(t, false, pendingAggregatesAreEqual(a, b))
})
t.Run("different aggregator index", func(t *testing.T) {
a := &ethpb.SignedAggregateAttestationAndProof{Message: &ethpb.AggregateAttestationAndProof{AggregatorIndex: 1}}
b := &ethpb.SignedAggregateAttestationAndProof{Message: &ethpb.AggregateAttestationAndProof{AggregatorIndex: 2}}
assert.Equal(t, false, pendingAggregatesAreEqual(a, b))
})
t.Run("different slot", func(t *testing.T) {
a := &ethpb.SignedAggregateAttestationAndProof{
Message: &ethpb.AggregateAttestationAndProof{
Expand Down
30 changes: 25 additions & 5 deletions beacon-chain/sync/pending_blocks_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package sync

import (
"context"
"encoding/hex"
"fmt"
"slices"
"sync"
Expand Down Expand Up @@ -44,11 +43,13 @@ func (s *Service) processPendingBlocksQueue() {
if !s.chainIsStarted() {
return
}

locker.Lock()
defer locker.Unlock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this deadlock ever happened in the wild

if err := s.processPendingBlocks(s.ctx); err != nil {
log.WithError(err).Debug("Could not process pending blocks")
}
locker.Unlock()
})
}

Expand Down Expand Up @@ -91,6 +92,8 @@ func (s *Service) processPendingBlocks(ctx context.Context) error {

// Process each block in the queue.
for _, b := range blocksInCache {
start := time.Now()

if err := blocks.BeaconBlockIsNil(b); err != nil {
continue
}
Expand Down Expand Up @@ -156,8 +159,14 @@ func (s *Service) processPendingBlocks(ctx context.Context) error {
if err := s.removeBlockFromQueue(b, blkRoot); err != nil {
return err
}
log.WithFields(logrus.Fields{"slot": slot, "blockRoot": hex.EncodeToString(bytesutil.Trunc(blkRoot[:]))}).Debug("Processed pending block and cleared it in cache")

log.WithFields(logrus.Fields{
"slot": slot,
"root": fmt.Sprintf("%#x", blkRoot),
"duration": time.Since(start),
}).Debug("Processed pending block and cleared it in cache")
}

span.End()
}
return s.sendBatchRootRequest(ctx, parentRoots, randGen)
Expand Down Expand Up @@ -379,6 +388,19 @@ func (s *Service) sendBatchRootRequest(ctx context.Context, roots [][32]byte, ra
req = roots[:maxReqBlock]
}

if logrus.GetLevel() >= logrus.DebugLevel {
rootsStr := make([]string, 0, len(roots))
for _, req := range roots {
rootsStr = append(rootsStr, fmt.Sprintf("%#x", req))
}

log.WithFields(logrus.Fields{
"peer": pid,
"count": len(req),
"roots": rootsStr,
}).Debug("Requesting blocks by root")
}

// Send the request to the peer.
if err := s.sendBeaconBlocksRequest(ctx, &req, pid); err != nil {
tracing.AnnotateError(span, err)
Expand Down Expand Up @@ -438,8 +460,6 @@ func (s *Service) filterOutPendingAndSynced(roots [][fieldparams.RootLength]byte
roots = append(roots[:i], roots[i+1:]...)
continue
}

log.WithField("blockRoot", fmt.Sprintf("%#x", r)).Debug("Requesting block by root")
}
return roots
}
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/verification/data_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func (dv *RODataColumnsVerifier) SidecarParentSeen(parentSeen func([fieldparams.
}

if !dv.fc.HasNode(parentRoot) {
return columnErrBuilder(errSidecarParentNotSeen)
return columnErrBuilder(errors.Wrapf(errSidecarParentNotSeen, "parent root: %#x", parentRoot))
}
}

Expand Down
3 changes: 3 additions & 0 deletions changelog/manu-agg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Changed

- Pending aggregates: When multiple aggregated attestations only differing by the aggregator index are in the pending queue, only process one of them.
Loading