Skip to content

Commit 227462d

Browse files
committed
Spec Atomicity bug updates
1 parent 54ef26b commit 227462d

File tree

6 files changed

+91
-40
lines changed

6 files changed

+91
-40
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ use crate::{metrics, BeaconChainError};
5050
use eth2::types::{
5151
EventKind, SseBlock, SseChainReorg, SseFinalizedCheckpoint, SseHead, SseLateHead, SyncDuty,
5252
};
53+
use fork_choice::{AttestationFromBlock, ForkChoice};
5354
use execution_layer::ExecutionLayer;
54-
use fork_choice::ForkChoice;
5555
use futures::channel::mpsc::Sender;
5656
use itertools::process_results;
5757
use itertools::Itertools;
@@ -1698,7 +1698,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
16981698

16991699
self.fork_choice
17001700
.write()
1701-
.on_attestation(self.slot()?, verified.indexed_attestation())
1701+
.on_attestation(
1702+
self.slot()?,
1703+
verified.indexed_attestation(),
1704+
AttestationFromBlock::False,
1705+
)
17021706
.map_err(Into::into)
17031707
}
17041708

@@ -2470,7 +2474,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
24702474
let indexed_attestation = get_indexed_attestation(committee.committee, attestation)
24712475
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
24722476

2473-
match fork_choice.on_attestation(current_slot, &indexed_attestation) {
2477+
match fork_choice.on_attestation(
2478+
current_slot,
2479+
&indexed_attestation,
2480+
AttestationFromBlock::True,
2481+
) {
24742482
Ok(()) => Ok(()),
24752483
// Ignore invalid attestations whilst importing attestations from a block. The
24762484
// block might be very old and therefore the attestations useless to fork choice.

beacon_node/beacon_chain/src/beacon_fork_choice_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ where
344344
pub struct PersistedForkChoiceStore {
345345
balances_cache: BalancesCache,
346346
time: Slot,
347-
finalized_checkpoint: Checkpoint,
348-
justified_checkpoint: Checkpoint,
347+
pub finalized_checkpoint: Checkpoint,
348+
pub justified_checkpoint: Checkpoint,
349349
justified_balances: Vec<u64>,
350350
best_justified_checkpoint: Checkpoint,
351351
}

beacon_node/beacon_chain/src/schema_change.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ pub fn migrate_schema<T: BeaconChainTypes>(
119119
.map_err(StoreError::SchemaMigrationError)?;
120120
};
121121

122+
migrate_schema_6::update_store_justified_checkpoint::<T>(
123+
&mut persisted_fork_choice,
124+
)
125+
.map_err(StoreError::SchemaMigrationError)?;
126+
122127
// Store the converted fork choice store under the same key.
123128
db.put_item::<PersistedForkChoice>(&FORK_CHOICE_DB_KEY, &persisted_fork_choice)?;
124129
}

beacon_node/beacon_chain/src/schema_change/migrate_schema_6.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,31 @@ use store::Error as StoreError;
2121
// selector.
2222
four_byte_option_impl!(four_byte_option_usize, usize);
2323

24+
pub(crate) fn update_store_justified_checkpoint<T: BeaconChainTypes>(
25+
persisted_fork_choice: &mut PersistedForkChoice,
26+
) -> Result<(), String> {
27+
let bytes = persisted_fork_choice.fork_choice.proto_array_bytes.clone();
28+
let container = SszContainer::from_ssz_bytes(bytes.as_slice()).unwrap();
29+
let mut fork_choice: ProtoArrayForkChoice = container.into();
30+
31+
let justified_checkpoint = fork_choice
32+
.core_proto_array()
33+
.nodes
34+
.iter()
35+
.find_map(|node| {
36+
(node.finalized_checkpoint
37+
== Some(persisted_fork_choice.fork_choice_store.finalized_checkpoint))
38+
.then(|| node.justified_checkpoint)
39+
.flatten()
40+
})
41+
.ok_or("Proto node with current finalized checkpoint not found")?;
42+
43+
fork_choice.core_proto_array_mut().justified_checkpoint = justified_checkpoint;
44+
persisted_fork_choice.fork_choice.proto_array_bytes = fork_choice.as_bytes();
45+
persisted_fork_choice.fork_choice_store.justified_checkpoint = justified_checkpoint;
46+
Ok(())
47+
}
48+
2449
pub(crate) fn update_with_reinitialized_fork_choice<T: BeaconChainTypes>(
2550
persisted_fork_choice: &mut PersistedForkChoice,
2651
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,

consensus/fork_choice/src/fork_choice.rs

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@ fn dequeue_attestations(
218218
std::mem::replace(queued_attestations, remaining)
219219
}
220220

221+
/// Denotes whether an attestation we are processing received from a block or from gossip.
222+
/// Equivalent to the `is_from_block` bool in:
223+
///
224+
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#validate_on_attestation
225+
pub enum AttestationFromBlock {
226+
True,
227+
False,
228+
}
229+
221230
/// Provides an implementation of "Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice":
222231
///
223232
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#ethereum-20-phase-0----beacon-chain-fork-choice
@@ -537,25 +546,9 @@ where
537546
if state.finalized_checkpoint().epoch > self.fc_store.finalized_checkpoint().epoch {
538547
self.fc_store
539548
.set_finalized_checkpoint(state.finalized_checkpoint());
540-
let finalized_slot =
541-
compute_start_slot_at_epoch::<E>(self.fc_store.finalized_checkpoint().epoch);
542-
543-
// Note: the `if` statement here is not part of the specification, but I claim that it
544-
// is an optimization and equivalent to the specification. See this PR for more
545-
// information:
546-
//
547-
// https://github.com/ethereum/eth2.0-specs/pull/1880
548-
if *self.fc_store.justified_checkpoint() != state.current_justified_checkpoint()
549-
&& (state.current_justified_checkpoint().epoch
550-
> self.fc_store.justified_checkpoint().epoch
551-
|| self
552-
.get_ancestor(self.fc_store.justified_checkpoint().root, finalized_slot)?
553-
!= Some(self.fc_store.finalized_checkpoint().root))
554-
{
555-
self.fc_store
556-
.set_justified_checkpoint(state.current_justified_checkpoint())
557-
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
558-
}
549+
self.fc_store
550+
.set_justified_checkpoint(state.current_justified_checkpoint())
551+
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
559552
}
560553

561554
let target_slot = block
@@ -629,6 +622,35 @@ where
629622
Ok(())
630623
}
631624

625+
/// Validates the `epoch` against the current time according to the fork choice store.
626+
///
627+
/// ## Specification
628+
///
629+
/// Equivalent to:
630+
///
631+
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#validate_target_epoch_against_current_time
632+
fn validate_target_epoch_against_current_time(
633+
&self,
634+
target_epoch: Epoch,
635+
) -> Result<(), InvalidAttestation> {
636+
let slot_now = self.fc_store.get_current_slot();
637+
let epoch_now = slot_now.epoch(E::slots_per_epoch());
638+
639+
// Attestation must be from the current or previous epoch.
640+
if target_epoch > epoch_now {
641+
return Err(InvalidAttestation::FutureEpoch {
642+
attestation_epoch: target_epoch,
643+
current_epoch: epoch_now,
644+
});
645+
} else if target_epoch + 1 < epoch_now {
646+
return Err(InvalidAttestation::PastEpoch {
647+
attestation_epoch: target_epoch,
648+
current_epoch: epoch_now,
649+
});
650+
}
651+
Ok(())
652+
}
653+
632654
/// Validates the `indexed_attestation` for application to fork choice.
633655
///
634656
/// ## Specification
@@ -639,6 +661,7 @@ where
639661
fn validate_on_attestation(
640662
&self,
641663
indexed_attestation: &IndexedAttestation<E>,
664+
is_from_block: AttestationFromBlock,
642665
) -> Result<(), InvalidAttestation> {
643666
// There is no point in processing an attestation with an empty bitfield. Reject
644667
// it immediately.
@@ -649,21 +672,10 @@ where
649672
return Err(InvalidAttestation::EmptyAggregationBitfield);
650673
}
651674

652-
let slot_now = self.fc_store.get_current_slot();
653-
let epoch_now = slot_now.epoch(E::slots_per_epoch());
654675
let target = indexed_attestation.data.target;
655676

656-
// Attestation must be from the current or previous epoch.
657-
if target.epoch > epoch_now {
658-
return Err(InvalidAttestation::FutureEpoch {
659-
attestation_epoch: target.epoch,
660-
current_epoch: epoch_now,
661-
});
662-
} else if target.epoch + 1 < epoch_now {
663-
return Err(InvalidAttestation::PastEpoch {
664-
attestation_epoch: target.epoch,
665-
current_epoch: epoch_now,
666-
});
677+
if matches!(is_from_block, AttestationFromBlock::False) {
678+
self.validate_target_epoch_against_current_time(target.epoch)?;
667679
}
668680

669681
if target.epoch != indexed_attestation.data.slot.epoch(E::slots_per_epoch()) {
@@ -746,6 +758,7 @@ where
746758
&mut self,
747759
current_slot: Slot,
748760
attestation: &IndexedAttestation<E>,
761+
is_from_block: AttestationFromBlock,
749762
) -> Result<(), Error<T::Error>> {
750763
// Ensure the store is up-to-date.
751764
self.update_time(current_slot)?;
@@ -767,7 +780,7 @@ where
767780
return Ok(());
768781
}
769782

770-
self.validate_on_attestation(attestation)?;
783+
self.validate_on_attestation(attestation, is_from_block)?;
771784

772785
if attestation.data.slot < self.fc_store.get_current_slot() {
773786
for validator_index in attestation.attesting_indices.iter() {

consensus/fork_choice/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ mod fork_choice;
22
mod fork_choice_store;
33

44
pub use crate::fork_choice::{
5-
Error, ForkChoice, InvalidAttestation, InvalidBlock, PayloadVerificationStatus,
6-
PersistedForkChoice, QueuedAttestation,
5+
AttestationFromBlock, Error, ForkChoice, InvalidAttestation, InvalidBlock,
6+
PayloadVerificationStatus, PersistedForkChoice, QueuedAttestation,
77
};
88
pub use fork_choice_store::ForkChoiceStore;
99
pub use proto_array::Block as ProtoBlock;

0 commit comments

Comments
 (0)