Skip to content

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

Merged
nalepae merged 19 commits into
developfrom
not-been-seen
Dec 19, 2025
Merged

Pending aggregates: When multiple aggregated attestations only differing by the aggregator index are in the pending queue, only process one of them.#16153
nalepae merged 19 commits into
developfrom
not-been-seen

Conversation

@nalepae

@nalepae nalepae commented Dec 17, 2025

Copy link
Copy Markdown
Contributor

What type of PR is this?
Other

What does this PR do? Why is it needed?
When an (potentially aggregated) attestation is received before the block being voted for, Prysm queues this attestation, then processes the queue when the block has been received.

This behavior is consistent with the Phase0 specification .

[IGNORE] The block being voted for (attestation.data.beacon_block_root) has been seen (via gossip or non-gossip sources) (a client MAY queue attestations for processing once block is retrieved).

Once the block being voted for is processed, previously queued (potentially aggregated) attestations are then processed, and broadcasted.

Processing (potentially aggregated) attestations takes some non negligible time. For this reason, (potentially aggregated) attestations are deduplicated before being introduced into the pending queue, to avoid eventually processing duplicates.

Before this PR, two aggregated attestations were considered duplicated if all of the following conditions were gathered:

  1. Attestations have the same version,
  2. Attestations have the same aggregator index (aka., the same validator aggregated them),
  3. Attestations have the same slot,
  4. Attestations have the same committee index, and
  5. Attestations have the same aggregation bits

Aggregated attestations are then broadcasted.
The final purpose of aggregated attestations is to be packed into the next block by the next proposer.
When packing attestations, the aggregator index is not used any more.

This pull request modifies the deduplication function used in the pending aggregated attestations queue by considering that multiple aggregated attestations only differing by the aggregator index are equivalent (removing 2. of the previous list.)

As a consequence, the count of aggregated attestations to be introduced in the pending queue is reduced from 1 aggregated attestation by aggregator to, in the best case, MAX_COMMITTEE_PER_SLOT=64.

Also, only a single aggregated attestation for a given version, slot, committee index and aggregation bits will be re-broadcasted. This is a correct behavior, since no data to be included in a block will be lost. (We can even say that this will reduce by a bit the total networking volume.)

How to test:

  1. Start a beacon node (preferably, on a slow computer) from a checkpoint.
  2. Filter logs containing Synced new block and Verified and saved pending attestations to pool. (You can pipe logs into grep -E "Synced new block|Verified and saved pending attestations to pool".
  • In Synced new block logs, monitor the sinceSlotStartTime value. This should monotonically decrease.
  • In Verified and saved pending attestations to pool, monitor the pendingAggregateAttAndProofCount value. It should be a "honest" value. "honest" is not really quantifiable here, since it depends on the aggregators. But it's likely to be less than 5*MAX_COMMITTEE_PER_SLOT=320.

Which issues(s) does this PR fix?

Partially fixes:

Other notes for review
Please read commit by commit, with commit messages.
The important commit is b748c04.

Acknowledgements

  • I have read CONTRIBUTING.md.
  • I have included a uniquely named changelog fragment file.
  • I have added a description with sufficient context for reviewers to understand this PR.
  • I have tested that my changes work as expected and I added a testing plan to the PR description (if applicable).

@nalepae nalepae changed the title Not been seen Stop considering the aggregator index to compare 2 aggregated attestations in the pending queue. Dec 17, 2025
…ing by the aggregator index are in the pending queue, only process one of them.
@nalepae nalepae changed the title Stop considering the aggregator index to compare 2 aggregated attestations in the pending queue. Pending aggregates: When multiple aggregated attestations only differing by the aggregator index are in the pending queue, only process one of them. Dec 18, 2025
@nalepae
nalepae marked this pull request as ready for review December 18, 2025 09:59

@potuz potuz left a comment

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.

LGTM, would feel better with an extra validation of my comment

}
}

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

Comment on lines -398 to -400
if a.AggregateAttestationAndProof().GetAggregatorIndex() != b.AggregateAttestationAndProof().GetAggregatorIndex() {
return false
}

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.

@nalepae
nalepae marked this pull request as draft December 18, 2025 13:48
@nalepae
nalepae marked this pull request as ready for review December 18, 2025 15:52
potuz
potuz previously approved these changes Dec 19, 2025

@potuz potuz left a comment

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.

LGTM

// Verify attestation target root is consistent with the head root.
// This verification is not in the spec, however we guard against it as it opens us up
// to weird edge cases during verification. The attestation technically could be used to add value to a block,
// but it's invalid in the spirit of the protocol. Here we choose safety over profit.

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 don't understand this good. Moving this offline

func pendingAggregatesAreEqual(a, b ethpb.SignedAggregateAttAndProof) bool {
// pendingAggregatesAreEqual checks if two pending aggregate attestations are equal.
// If filterOnAggregatorIndex is false, two aggregates only differ by their aggregator index will be considered equal.
func pendingAggregatesAreEqual(a, b ethpb.SignedAggregateAttAndProof, filterOnAggregatorIndex bool) bool {

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.

for the record I don't mind boolean controllers, but even some linters would recommend here a named wrapper that has the extra validation. I think it's fine as is.

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.

Fixed in 4eb55cd.


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

}
}

initialMissingRootCount := len(missingIndicesByRoot)

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

durationAtts := time.Since(startAtts)

log.WithFields(logrus.Fields{
"blockRoot": fmt.Sprintf("%#x", blockRoot),

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.

it looks to me we are just passing blockRoot as an argument just for logging purpose, but you dont really need to pass it, we could get the blockRoot from the attestation it self, it's just attestation.data.block_root

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.

Fixed in df3b0da.

Comment on lines +424 to +427
if filterOnAggregatorIndex {
if a.AggregateAttestationAndProof().GetAggregatorIndex() != b.AggregateAttestationAndProof().GetAggregatorIndex() {
return false
}

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.

you may want to add a unit test just for this case as i haven't really see them yet

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.

Fixed in 7dfaa90.

@nalepae

nalepae commented Dec 19, 2025

Copy link
Copy Markdown
Contributor Author

#16153 (comment)

Yeah actually it is in a totally separate commit (that's why I asked to read commit by commit).
For the context, when initially working on this PR, I thought the bottleneck was the sidecars fetching process, not the attestation handling. That's why I added this field :)

@nalepae
nalepae added this pull request to the merge queue Dec 19, 2025
Merged via the queue into develop with commit 2ac30f5 Dec 19, 2025
18 checks passed
@nalepae
nalepae deleted the not-been-seen branch December 19, 2025 14:14

@meny3437-blip meny3437-blip left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants