# Analyzing Prysm's Proposer Attestation Packing in Pectra
## Background
Attestation format on the beacon chain changed in [EIP-7549]([https://eips.ethereum.org/EIPS/eip-7549](https://eips.ethereum.org/EIPS/eip-7549)) and that's coming in the [next network upgrade](https://blog.ethereum.org/2025/03/18/hoodi-holesky) Pectra. We want to understand how this would affect proposer packing attestations in block. It's an important question because attestations in a block not only contribute to attester and proposer rewards but also contribute to the chain's liveness and safety properties.
## Data collection
This post documents how a Prysm proposer packs attestations from March 28 22:00 to March 30 14:30 PST on the Hoodie testnet. Prysm recently updated its attestation packing scheme from sorting by highest slot to sorting by highest reward, based on the following spec code.
```python
participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot)
if data.target.epoch == get_current_epoch(state):
epoch_participation = state.current_epoch_participation
else:
epoch_participation = state.previous_epoch_participation
proposer_reward_numerator = 0
for index in get_attesting_indices(state, attestation):
for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS):
if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
proposer_reward_numerator += get_base_reward(state, index) * weight
```
A natural question following this change is how it performs in a testnet environment that has already forked to Pectra. Are we seeing anything unexpected with the current proposer packing scheme. We assume the audience already understands that Pectra changes the beacon attestation structure from 1D (aggregation bits) to 2D (aggregation plus committee bits scheme). If not, please refer to [EIP-7549]([https://eips.ethereum.org/EIPS/eip-7549](https://eips.ethereum.org/EIPS/eip-7549)).
We want to answer the following questions
- How many aggregated attestations exist in the mempool when the proposer builds a block
- How long it takes to sort the attestations and pack them into the block
- What are the qualities of the top 8 packed attestations (index 0 to 7)
- How many aggregation bits they contain
- How many committee bits they contain
- How many of the attesters had already voted before this attestation
- How far the attestation slot is from the block slot
For this experiment, we run a node and subscribe to all attestation subnets and aggregate them at various intervals, we also pretend to be a proposer every slot and pack attestations at second 0 of the slot. Then, around the 1 to 3 second mark, whenever we see a block over gossip that contains overlapping attestations, we remove those attestations from the pool.
## Results
### Packing / sorting attestation time
We first look at how many attestations are in the pool. Over ~40 hours, there are on average 40 to 65 attestations in the pool when we need to pack a block. So for every block, that’s the number of attestations being considered. Of course, this number may vary between Hoodie testnet and mainnet depending on the active validator set size, but they should be similar.

Packing attestations into a block mostly takes about 50-100ms. That includes grabbing attestations from the pool, filtering, verifying, aggregating, and sorting them by reward. It’s an end-to-end operation.

Sorting attestations by reward mostly falls in the 25 to 50ms range. This is the time it takes to sort all attestations by reward using the spec code mentioned above. The majority of the time is spent counting the reward of individual attestations, which can be further optimized.

### Index 0 attestation
We focus on the attestation at block index 0 over the last 40 hours, which should be the most profitable attestation in the block. As expected, most of the attestations at index 0 are “perfectly” built, with 28k aggregation bits and 64 committee bits, zero overlap, and minimal slot delay. Hoodie participation is consistently around 85%, so 1.08M active validators * 0.85 / 32 gets us to ~28k.


### Index 1 attestation
Then we focus on the attestation at block index 1 over the same time frame. This is the second most profitable attestation in the block. It already looks quite different from the attestation at index 0. Aggregation bits range from 2k to 20k, and we start seeing overlapping votes below 25%. Committee bits vary a lot but are mostly above 8, and inclusion delay now spans 2 to 4 slots. These are attestations are old and the left over from previous slot proposer.


### Index 2 attestation
Then we focus on the attestation at block index 2 over the same time frame. This is the third most profitable attestation in the block. As expected, the index 2 attestation looks more degraded than the one at index 1. Both aggregation bits and committee bits shift toward lower counts. Overlap in the 25% to 50% range becomes far more common, and inclusion distance often goes above 4 slots or more.


### Index 3-7 attestations
To avoid repeating similar pattern, we'll now look at attestations from index 3 to index 7 all at once. As expected, as the index gets higher, we see less aggregation, lower committee bit counts become common, and overlap in the 50% to 75% range is very common. We also see inclusion of attestations much older than 16 to 32 slots, as they are still considered "profitable".


Next steps:
- Some client teams are still fine-tuning attestation packing in blocks, so we'll likely collect another round of experiments once all client changes are in
- We need to answer the question: are we happy with the current attestation packing scheme, or are there still improvements to be made
- Explore whether there’s anything interesting we can tweak about sorting by profitability
References:
- Prysm PR that changes attestation sorting by reward: https://github.com/prysmaticlabs/prysm/pull/15093
- Prysm PR that packs attestations every slot and captures metrics on how well attestations are packed: https://github.com/prysmaticlabs/prysm/pull/15103