Skip to content

Commit def8e8e

Browse files
committed
refactor process_attestations
1 parent 8b5d71c commit def8e8e

File tree

8 files changed

+357
-250
lines changed

8 files changed

+357
-250
lines changed

consensus/state_processing/src/common/get_attestation_participation.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use integer_sqrt::IntegerSquareRoot;
2+
use safe_arith::SafeArith;
23
use smallvec::SmallVec;
34
use types::{AttestationData, BeaconState, ChainSpec, EthSpec};
45
use types::{
@@ -49,7 +50,10 @@ pub fn get_attestation_participation_flag_indices<E: EthSpec>(
4950
} else {
5051
// For non same-slot attestations, check execution payload availability
5152
// TODO(EIP7732) Discuss if we want to return new error BeaconStateError::InvalidExecutionPayloadAvailabilityIndex here for bit out of bounds or use something like BeaconStateError::InvalidBitfield
52-
let slot_index = data.slot.as_usize() % E::slots_per_historical_root();
53+
let slot_index = data
54+
.slot
55+
.as_usize()
56+
.safe_rem(E::slots_per_historical_root())?;
5357
state
5458
.execution_payload_availability()?
5559
.get(slot_index)
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
use types::{AttestationData, BeaconState, BeaconStateError, EthSpec};
22

33
/// Checks if the attestation was for the block proposed at the attestation slot.
4-
///
5-
/// Returns true if:
6-
/// - The attestation is for slot 0 (genesis), OR
7-
/// - The attestation's beacon_block_root matches the block actually proposed at that slot
8-
/// AND it's different from the previous slot's block (indicating no skip)
94
pub fn is_attestation_same_slot<E: EthSpec>(
105
state: &BeaconState<E>,
116
data: &AttestationData,
@@ -14,8 +9,8 @@ pub fn is_attestation_same_slot<E: EthSpec>(
149
return Ok(true);
1510
}
1611

17-
let is_matching_block_root = data.beacon_block_root == *state.get_block_root(data.slot)?;
18-
let is_current_block_root = data.beacon_block_root != *state.get_block_root(data.slot - 1)?;
12+
let is_matching_block_root = &data.beacon_block_root == state.get_block_root(data.slot)?;
13+
let is_current_block_root = &data.beacon_block_root != state.get_block_root(data.slot - 1)?;
1914

2015
Ok(is_matching_block_root && is_current_block_root)
2116
}

consensus/state_processing/src/per_block_processing.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub mod block_signature_verifier;
2929
pub mod deneb;
3030
pub mod errors;
3131
mod is_valid_indexed_attestation;
32+
pub mod process_attestations;
3233
pub mod process_operations;
3334
pub mod signature_sets;
3435
pub mod tests;

consensus/state_processing/src/per_block_processing/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ pub enum BlockProcessingError {
9191
},
9292
WithdrawalCredentialsInvalid,
9393
PendingAttestationInElectra,
94+
/// Builder payment index out of bounds (Gloas)
95+
BuilderPaymentIndexOutOfBounds(usize),
9496
}
9597

9698
impl From<BeaconStateError> for BlockProcessingError {
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
use super::*;
2+
use crate::common::{
3+
get_attestation_participation_flag_indices, increase_balance, is_attestation_same_slot,
4+
};
5+
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
6+
use safe_arith::SafeArith;
7+
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
8+
9+
pub mod base {
10+
use super::*;
11+
12+
/// Validates each `Attestation` and updates the state, short-circuiting on an invalid object.
13+
///
14+
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
15+
/// an `Err` describing the invalid object or cause of failure.
16+
pub fn process_attestations<'a, E: EthSpec, I>(
17+
state: &mut BeaconState<E>,
18+
attestations: I,
19+
verify_signatures: VerifySignatures,
20+
ctxt: &mut ConsensusContext<E>,
21+
spec: &ChainSpec,
22+
) -> Result<(), BlockProcessingError>
23+
where
24+
I: Iterator<Item = AttestationRef<'a, E>>,
25+
{
26+
// Ensure required caches are all built. These should be no-ops during regular operation.
27+
state.build_committee_cache(RelativeEpoch::Current, spec)?;
28+
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
29+
initialize_epoch_cache(state, spec)?;
30+
initialize_progressive_balances_cache(state, spec)?;
31+
state.build_slashings_cache()?;
32+
33+
let proposer_index = ctxt.get_proposer_index(state, spec)?;
34+
35+
// Verify and apply each attestation.
36+
for (i, attestation) in attestations.enumerate() {
37+
verify_attestation_for_block_inclusion(
38+
state,
39+
attestation,
40+
ctxt,
41+
verify_signatures,
42+
spec,
43+
)
44+
.map_err(|e| e.into_with_index(i))?;
45+
46+
let AttestationRef::Base(attestation) = attestation else {
47+
// Pending attestations have been deprecated in a altair, this branch should
48+
// never happen
49+
return Err(BlockProcessingError::PendingAttestationInElectra);
50+
};
51+
52+
let pending_attestation = PendingAttestation {
53+
aggregation_bits: attestation.aggregation_bits.clone(),
54+
data: attestation.data.clone(),
55+
inclusion_delay: state.slot().safe_sub(attestation.data.slot)?.as_u64(),
56+
proposer_index,
57+
};
58+
59+
if attestation.data.target.epoch == state.current_epoch() {
60+
state
61+
.as_base_mut()?
62+
.current_epoch_attestations
63+
.push(pending_attestation)?;
64+
} else {
65+
state
66+
.as_base_mut()?
67+
.previous_epoch_attestations
68+
.push(pending_attestation)?;
69+
}
70+
}
71+
72+
Ok(())
73+
}
74+
}
75+
76+
pub mod altair_gloas {
77+
use super::*;
78+
use crate::common::update_progressive_balances_cache::update_progressive_balances_on_attestation;
79+
80+
pub fn process_attestation<E: EthSpec>(
81+
state: &mut BeaconState<E>,
82+
attestation: AttestationRef<E>,
83+
att_index: usize,
84+
ctxt: &mut ConsensusContext<E>,
85+
verify_signatures: VerifySignatures,
86+
spec: &ChainSpec,
87+
) -> Result<(), BlockProcessingError> {
88+
if !state.fork_name_unchecked().gloas_enabled() {
89+
return altair::process_attestation(
90+
state,
91+
attestation,
92+
att_index,
93+
ctxt,
94+
verify_signatures,
95+
spec,
96+
);
97+
}
98+
99+
gloas::process_attestation(state, attestation, att_index, ctxt, verify_signatures, spec)
100+
}
101+
102+
pub fn process_attestations<'a, E: EthSpec, I>(
103+
state: &mut BeaconState<E>,
104+
attestations: I,
105+
verify_signatures: VerifySignatures,
106+
ctxt: &mut ConsensusContext<E>,
107+
spec: &ChainSpec,
108+
) -> Result<(), BlockProcessingError>
109+
where
110+
I: Iterator<Item = AttestationRef<'a, E>>,
111+
{
112+
attestations.enumerate().try_for_each(|(i, attestation)| {
113+
process_attestation(state, attestation, i, ctxt, verify_signatures, spec)
114+
})
115+
}
116+
117+
pub mod altair {
118+
use super::*;
119+
120+
pub fn process_attestation<E: EthSpec>(
121+
state: &mut BeaconState<E>,
122+
attestation: AttestationRef<E>,
123+
att_index: usize,
124+
ctxt: &mut ConsensusContext<E>,
125+
verify_signatures: VerifySignatures,
126+
spec: &ChainSpec,
127+
) -> Result<(), BlockProcessingError> {
128+
let proposer_index = ctxt.get_proposer_index(state, spec)?;
129+
let previous_epoch = ctxt.previous_epoch;
130+
let current_epoch = ctxt.current_epoch;
131+
132+
let indexed_att = verify_attestation_for_block_inclusion(
133+
state,
134+
attestation,
135+
ctxt,
136+
verify_signatures,
137+
spec,
138+
)
139+
.map_err(|e| e.into_with_index(att_index))?;
140+
141+
// Matching roots, participation flag indices
142+
let data = attestation.data();
143+
let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64();
144+
let participation_flag_indices =
145+
get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?;
146+
147+
// Update epoch participation flags.
148+
let mut proposer_reward_numerator = 0;
149+
for index in indexed_att.attesting_indices_iter() {
150+
let index = *index as usize;
151+
152+
let validator_effective_balance =
153+
state.epoch_cache().get_effective_balance(index)?;
154+
let validator_slashed = state.slashings_cache().is_slashed(index);
155+
156+
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
157+
let epoch_participation = state.get_epoch_participation_mut(
158+
data.target.epoch,
159+
previous_epoch,
160+
current_epoch,
161+
)?;
162+
163+
if participation_flag_indices.contains(&flag_index) {
164+
let validator_participation = epoch_participation
165+
.get_mut(index)
166+
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
167+
168+
if !validator_participation.has_flag(flag_index)? {
169+
validator_participation.add_flag(flag_index)?;
170+
proposer_reward_numerator
171+
.safe_add_assign(state.get_base_reward(index)?.safe_mul(weight)?)?;
172+
173+
update_progressive_balances_on_attestation(
174+
state,
175+
data.target.epoch,
176+
flag_index,
177+
validator_effective_balance,
178+
validator_slashed,
179+
)?;
180+
}
181+
}
182+
}
183+
}
184+
185+
let proposer_reward_denominator = WEIGHT_DENOMINATOR
186+
.safe_sub(PROPOSER_WEIGHT)?
187+
.safe_mul(WEIGHT_DENOMINATOR)?
188+
.safe_div(PROPOSER_WEIGHT)?;
189+
let proposer_reward =
190+
proposer_reward_numerator.safe_div(proposer_reward_denominator)?;
191+
increase_balance(state, proposer_index as usize, proposer_reward)?;
192+
Ok(())
193+
}
194+
}
195+
196+
pub mod gloas {
197+
use super::*;
198+
199+
pub fn process_attestation<E: EthSpec>(
200+
state: &mut BeaconState<E>,
201+
attestation: AttestationRef<E>,
202+
att_index: usize,
203+
ctxt: &mut ConsensusContext<E>,
204+
verify_signatures: VerifySignatures,
205+
spec: &ChainSpec,
206+
) -> Result<(), BlockProcessingError> {
207+
let proposer_index = ctxt.get_proposer_index(state, spec)?;
208+
let previous_epoch = ctxt.previous_epoch;
209+
let current_epoch = ctxt.current_epoch;
210+
211+
let indexed_att = verify_attestation_for_block_inclusion(
212+
state,
213+
attestation,
214+
ctxt,
215+
verify_signatures,
216+
spec,
217+
)
218+
.map_err(|e| e.into_with_index(att_index))?;
219+
220+
// Matching roots, participation flag indices
221+
let data = attestation.data();
222+
let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64();
223+
let participation_flag_indices =
224+
get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?;
225+
226+
// [New in EIP-7732]
227+
let current_epoch_target = data.target.epoch == state.current_epoch();
228+
let slot_mod = data
229+
.slot
230+
.as_usize()
231+
.safe_rem(E::slots_per_epoch() as usize)?;
232+
let payment_index = if current_epoch_target {
233+
(E::slots_per_epoch() as usize).safe_add(slot_mod)?
234+
} else {
235+
slot_mod
236+
};
237+
// Accumulate weight for same-slot attestations
238+
let mut accumulated_weight = 0;
239+
240+
// Update epoch participation flags.
241+
let mut proposer_reward_numerator = 0;
242+
for index in indexed_att.attesting_indices_iter() {
243+
let index = *index as usize;
244+
245+
let validator_effective_balance =
246+
state.epoch_cache().get_effective_balance(index)?;
247+
let validator_slashed = state.slashings_cache().is_slashed(index);
248+
249+
// [New in EIP7732]
250+
// For same-slot attestations, check if we're setting any new flags
251+
// If we are, this validator hasn't contributed to this slot's quorum yet
252+
let mut will_set_new_flag = false;
253+
254+
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
255+
let epoch_participation = state.get_epoch_participation_mut(
256+
data.target.epoch,
257+
previous_epoch,
258+
current_epoch,
259+
)?;
260+
261+
if participation_flag_indices.contains(&flag_index) {
262+
let validator_participation = epoch_participation
263+
.get_mut(index)
264+
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
265+
266+
if !validator_participation.has_flag(flag_index)? {
267+
validator_participation.add_flag(flag_index)?;
268+
proposer_reward_numerator
269+
.safe_add_assign(state.get_base_reward(index)?.safe_mul(weight)?)?;
270+
will_set_new_flag = true;
271+
272+
update_progressive_balances_on_attestation(
273+
state,
274+
data.target.epoch,
275+
flag_index,
276+
validator_effective_balance,
277+
validator_slashed,
278+
)?;
279+
}
280+
}
281+
}
282+
283+
// Check that payment_index is valid and get payment amount
284+
let builder_payments = state.builder_pending_payments_mut()?;
285+
let payment_amount = builder_payments
286+
.get(payment_index)
287+
.ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(
288+
payment_index,
289+
))?
290+
.withdrawal
291+
.amount;
292+
293+
// Collect validators for Gloas builder payment processing
294+
// We will only add weight for same-slot attestations when any new flag is set
295+
// This ensures each validator contributes exactly once per slot
296+
if will_set_new_flag && is_attestation_same_slot(state, data)? && payment_amount > 0
297+
{
298+
accumulated_weight.safe_add_assign(validator_effective_balance)?;
299+
}
300+
}
301+
302+
let proposer_reward_denominator = WEIGHT_DENOMINATOR
303+
.safe_sub(PROPOSER_WEIGHT)?
304+
.safe_mul(WEIGHT_DENOMINATOR)?
305+
.safe_div(PROPOSER_WEIGHT)?;
306+
let proposer_reward =
307+
proposer_reward_numerator.safe_div(proposer_reward_denominator)?;
308+
increase_balance(state, proposer_index as usize, proposer_reward)?;
309+
310+
// Update builder payment weight
311+
if accumulated_weight > 0 {
312+
let builder_payments = state.builder_pending_payments_mut()?;
313+
let payment = builder_payments.get_mut(payment_index).ok_or(
314+
BlockProcessingError::BuilderPaymentIndexOutOfBounds(payment_index),
315+
)?;
316+
payment.weight.safe_add_assign(accumulated_weight)?;
317+
}
318+
319+
Ok(())
320+
}
321+
}
322+
}

0 commit comments

Comments
 (0)