Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use derivative::Derivative;
use slot_clock::SlotClock;
use std::time::Duration;
use strum::AsRefStr;
use types::LightClientFinalityUpdate;
use types::{Hash256, LightClientFinalityUpdate, Slot};

/// Returned when a light client finality update was not successfully verified. It might not have been verified for
/// two reasons:
Expand All @@ -21,12 +21,37 @@ pub enum Error {
///
/// Assuming the local clock is correct, the peer has sent an invalid message.
TooEarly,
/// Light client finality update message does not match the locally constructed one.
InvalidLightClientFinalityUpdate,
/// Light client finalized update message does not match the locally constructed one, it has a
/// different signature slot.
MismatchedSignatureSlot { local: Slot, observed: Slot },
/// Light client finalized update message does not match the locally constructed one, it has a
/// different finalized block header for the same signature slot.
MismatchedFinalizedHeader {
local_finalized_header_root: Hash256,
observed_finalized_header_root: Hash256,
signature_slot: Slot,
},
/// Light client finalized update message does not match the locally constructed one, it has a
/// different attested block header for the same signature slot and finalized header.
MismatchedAttestedHeader {
local_attested_header_root: Hash256,
observed_attested_header_root: Hash256,
finalized_header_root: Hash256,
signature_slot: Slot,
},
/// Light client finalized update message does not match the locally constructed one, it has a
/// different proof or sync aggregate for the same slot, attested header and finalized header.
MismatchedProofOrSyncAggregate {
attested_header_root: Hash256,
finalized_header_root: Hash256,
signature_slot: Slot,
},
/// Signature slot start time is none.
SigSlotStartIsNone,
/// Failed to construct a LightClientFinalityUpdate from state.
FailedConstructingUpdate,
/// Silently ignore this light client finality update
Ignore,
}

/// Wraps a `LightClientFinalityUpdate` that has been verified for propagation on the gossip network.
Expand All @@ -48,7 +73,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientFinalityUpdate<T> {
// verify that enough time has passed for the block to have been propagated
let start_time = chain
.slot_clock
.start_of(*rcv_finality_update.signature_slot())
.start_of(rcv_finality_update.signature_slot())
.ok_or(Error::SigSlotStartIsNone)?;
let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0);
if seen_timestamp + chain.spec.maximum_gossip_clock_disparity()
Expand All @@ -57,16 +82,76 @@ impl<T: BeaconChainTypes> VerifiedLightClientFinalityUpdate<T> {
return Err(Error::TooEarly);
}

if let Some(latest_broadcasted_finality_update) = chain
.light_client_server_cache
.get_latest_broadcasted_finality_update()
{
// Ignore the incoming finality update if we've already broadcasted it
if latest_broadcasted_finality_update == rcv_finality_update {
return Err(Error::Ignore);
}

// Ignore the incoming finality update if the latest broadcasted attested header slot
// is greater than the incoming attested header slot.
if latest_broadcasted_finality_update.get_attested_header_slot()
> rcv_finality_update.get_attested_header_slot()
{
return Err(Error::Ignore);
}
}

let latest_finality_update = chain
.light_client_server_cache
.get_latest_finality_update()
.ok_or(Error::FailedConstructingUpdate)?;

// verify that the gossiped finality update is the same as the locally constructed one.
// Ignore the incoming finality update if the latest constructed attested header slot
// is greater than the incoming attested header slot.
if latest_finality_update.get_attested_header_slot()
> rcv_finality_update.get_attested_header_slot()
{
return Err(Error::Ignore);
}

// Verify that the gossiped finality update is the same as the locally constructed one.
if latest_finality_update != rcv_finality_update {
return Err(Error::InvalidLightClientFinalityUpdate);
let signature_slot = latest_finality_update.signature_slot();
if signature_slot != rcv_finality_update.signature_slot() {
return Err(Error::MismatchedSignatureSlot {
local: signature_slot,
observed: rcv_finality_update.signature_slot(),
});
}
let local_finalized_header_root = latest_finality_update.get_finalized_header_root();
let observed_finalized_header_root = rcv_finality_update.get_finalized_header_root();
if local_finalized_header_root != observed_finalized_header_root {
return Err(Error::MismatchedFinalizedHeader {
local_finalized_header_root,
observed_finalized_header_root,
signature_slot,
});
}
let local_attested_header_root = latest_finality_update.get_attested_header_root();
let observed_attested_header_root = rcv_finality_update.get_attested_header_root();
if local_attested_header_root != observed_attested_header_root {
return Err(Error::MismatchedAttestedHeader {
local_attested_header_root,
observed_attested_header_root,
finalized_header_root: local_finalized_header_root,
signature_slot,
});
}
return Err(Error::MismatchedProofOrSyncAggregate {
attested_header_root: local_attested_header_root,
finalized_header_root: local_finalized_header_root,
signature_slot,
});
}

chain
.light_client_server_cache
.set_latest_broadcasted_finality_update(rcv_finality_update.clone());

Ok(Self {
light_client_finality_update: rcv_finality_update,
seen_timestamp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use eth2::types::Hash256;
use slot_clock::SlotClock;
use std::time::Duration;
use strum::AsRefStr;
use types::LightClientOptimisticUpdate;
use types::{LightClientOptimisticUpdate, Slot};

/// Returned when a light client optimistic update was not successfully verified. It might not have been verified for
/// two reasons:
Expand All @@ -22,14 +22,30 @@ pub enum Error {
///
/// Assuming the local clock is correct, the peer has sent an invalid message.
TooEarly,
/// Light client optimistic update message does not match the locally constructed one.
InvalidLightClientOptimisticUpdate,
/// Light client optimistic update message does not match the locally constructed one, it has a
/// different signature slot.
MismatchedSignatureSlot { local: Slot, observed: Slot },
/// Light client optimistic update message does not match the locally constructed one, it has a
/// different block header at the same slot.
MismatchedAttestedHeader {
local_attested_header_root: Hash256,
observed_attested_header_root: Hash256,
signature_slot: Slot,
},
/// Light client optimistic update message does not match the locally constructed one, it has a
/// different sync aggregate for the same slot and attested header.
MismatchedSyncAggregate {
attested_header_root: Hash256,
signature_slot: Slot,
},
/// Signature slot start time is none.
SigSlotStartIsNone,
/// Failed to construct a LightClientOptimisticUpdate from state.
FailedConstructingUpdate,
/// Unknown block with parent root.
UnknownBlockParentRoot(Hash256),
/// Silently ignore this light client optimistic update
Ignore,
}

/// Wraps a `LightClientOptimisticUpdate` that has been verified for propagation on the gossip network.
Expand All @@ -52,7 +68,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
// verify that enough time has passed for the block to have been propagated
let start_time = chain
.slot_clock
.start_of(*rcv_optimistic_update.signature_slot())
.start_of(rcv_optimistic_update.signature_slot())
.ok_or(Error::SigSlotStartIsNone)?;
let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0);
if seen_timestamp + chain.spec.maximum_gossip_clock_disparity()
Expand All @@ -61,6 +77,22 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
return Err(Error::TooEarly);
}

if let Some(latest_broadcasted_optimistic_update) = chain
.light_client_server_cache
.get_latest_broadcasted_optimistic_update()
{
// Ignore the incoming optimistic update if we've already broadcasted it
if latest_broadcasted_optimistic_update == rcv_optimistic_update {
return Err(Error::Ignore);
}

// Ignore the incoming optimistic update if the latest broadcasted slot
// is greater than the incoming slot.
if latest_broadcasted_optimistic_update.get_slot() > rcv_optimistic_update.get_slot() {
return Err(Error::Ignore);
}
}

let head = chain.canonical_head.cached_head();
let head_block = &head.snapshot.beacon_block;
// check if we can process the optimistic update immediately
Expand All @@ -76,11 +108,40 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
.get_latest_optimistic_update()
.ok_or(Error::FailedConstructingUpdate)?;

// verify that the gossiped optimistic update is the same as the locally constructed one.
// Ignore the incoming optimistic update if the latest constructed slot
// is greater than the incoming slot.
if latest_optimistic_update.get_slot() > rcv_optimistic_update.get_slot() {
return Err(Error::Ignore);
}

// Verify that the gossiped optimistic update is the same as the locally constructed one.
if latest_optimistic_update != rcv_optimistic_update {
return Err(Error::InvalidLightClientOptimisticUpdate);
let signature_slot = latest_optimistic_update.signature_slot();
if signature_slot != rcv_optimistic_update.signature_slot() {
return Err(Error::MismatchedSignatureSlot {
local: signature_slot,
observed: rcv_optimistic_update.signature_slot(),
});
}
let local_attested_header_root = latest_optimistic_update.get_canonical_root();
let observed_attested_header_root = rcv_optimistic_update.get_canonical_root();
if local_attested_header_root != observed_attested_header_root {
return Err(Error::MismatchedAttestedHeader {
local_attested_header_root,
observed_attested_header_root,
signature_slot,
});
}
return Err(Error::MismatchedSyncAggregate {
attested_header_root: local_attested_header_root,
signature_slot,
});
}

chain
.light_client_server_cache
.set_latest_broadcasted_optimistic_update(rcv_optimistic_update.clone());

let parent_root = rcv_optimistic_update.get_parent_root();
Ok(Self {
light_client_optimistic_update: rcv_optimistic_update,
Expand Down
85 changes: 85 additions & 0 deletions beacon_node/beacon_chain/src/light_client_server_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub struct LightClientServerCache<T: BeaconChainTypes> {
latest_written_current_sync_committee: RwLock<Option<Arc<SyncCommittee<T::EthSpec>>>>,
/// Caches state proofs by block root
prev_block_cache: Mutex<lru::LruCache<Hash256, LightClientCachedData<T::EthSpec>>>,
/// Tracks the latest broadcasted finality update
latest_broadcasted_finality_update: RwLock<Option<LightClientFinalityUpdate<T::EthSpec>>>,
/// Tracks the latest broadcasted optimistic update
latest_broadcasted_optimistic_update: RwLock<Option<LightClientOptimisticUpdate<T::EthSpec>>>,
}

impl<T: BeaconChainTypes> LightClientServerCache<T> {
Expand All @@ -49,6 +53,8 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
latest_optimistic_update: None.into(),
latest_light_client_update: None.into(),
latest_written_current_sync_committee: None.into(),
latest_broadcasted_finality_update: None.into(),
latest_broadcasted_optimistic_update: None.into(),
prev_block_cache: lru::LruCache::new(PREV_BLOCK_CACHE_SIZE).into(),
}
}
Expand Down Expand Up @@ -334,10 +340,89 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
Ok(new_value)
}

/// Checks if we've already broadcasted the latest finality update.
/// If we haven't, update the `latest_broadcasted_finality_update` cache
/// and return the latest finality update for broadcasting, else return `None`.
pub fn should_broadcast_latest_finality_update(
&self,
) -> Option<LightClientFinalityUpdate<T::EthSpec>> {
if let Some(latest_finality_update) = self.get_latest_finality_update() {
let latest_broadcasted_finality_update = self.get_latest_broadcasted_finality_update();
match latest_broadcasted_finality_update {
Some(latest_broadcasted_finality_update) => {
if latest_broadcasted_finality_update != latest_finality_update {
self.set_latest_broadcasted_finality_update(latest_finality_update.clone());
return Some(latest_finality_update);
}
}
None => {
self.set_latest_broadcasted_finality_update(latest_finality_update.clone());
return Some(latest_finality_update);
}
}
}

None
}

pub fn get_latest_finality_update(&self) -> Option<LightClientFinalityUpdate<T::EthSpec>> {
self.latest_finality_update.read().clone()
}

pub fn get_latest_broadcasted_optimistic_update(
&self,
) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
self.latest_broadcasted_optimistic_update.read().clone()
}

pub fn get_latest_broadcasted_finality_update(
&self,
) -> Option<LightClientFinalityUpdate<T::EthSpec>> {
self.latest_broadcasted_finality_update.read().clone()
}

pub fn set_latest_broadcasted_optimistic_update(
&self,
optimistic_update: LightClientOptimisticUpdate<T::EthSpec>,
) {
*self.latest_broadcasted_optimistic_update.write() = Some(optimistic_update.clone());
}

pub fn set_latest_broadcasted_finality_update(
&self,
finality_update: LightClientFinalityUpdate<T::EthSpec>,
) {
*self.latest_broadcasted_finality_update.write() = Some(finality_update.clone());
}

/// Checks if we've already broadcasted the latest optimistic update.
/// If we haven't, update the `latest_broadcasted_optimistic_update` cache
/// and return the latest optimistic update for broadcasting, else return `None`.
pub fn should_broadcast_latest_optimistic_update(
&self,
) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
if let Some(latest_optimistic_update) = self.get_latest_optimistic_update() {
let latest_broadcasted_optimistic_update =
self.get_latest_broadcasted_optimistic_update();
match latest_broadcasted_optimistic_update {
Some(latest_broadcasted_optimistic_update) => {
if latest_broadcasted_optimistic_update != latest_optimistic_update {
self.set_latest_broadcasted_optimistic_update(
latest_optimistic_update.clone(),
);
return Some(latest_optimistic_update);
}
}
None => {
self.set_latest_broadcasted_optimistic_update(latest_optimistic_update.clone());
return Some(latest_optimistic_update);
}
}
}

None
}

pub fn get_latest_optimistic_update(&self) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
self.latest_optimistic_update.read().clone()
}
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2600,7 +2600,7 @@ pub fn serve<T: BeaconChainTypes>(

let fork_name = chain
.spec
.fork_name_at_slot::<T::EthSpec>(*update.signature_slot());
.fork_name_at_slot::<T::EthSpec>(update.signature_slot());
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
Expand Down
Loading