Skip to content

Commit bd8a2a8

Browse files
authored
Gossip recently computed light client data (#7023)
1 parent 56485cc commit bd8a2a8

File tree

9 files changed

+305
-19
lines changed

9 files changed

+305
-19
lines changed

beacon_node/beacon_chain/src/light_client_finality_update_verification.rs

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use derivative::Derivative;
33
use slot_clock::SlotClock;
44
use std::time::Duration;
55
use strum::AsRefStr;
6-
use types::LightClientFinalityUpdate;
6+
use types::{Hash256, LightClientFinalityUpdate, Slot};
77

88
/// Returned when a light client finality update was not successfully verified. It might not have been verified for
99
/// two reasons:
@@ -21,12 +21,37 @@ pub enum Error {
2121
///
2222
/// Assuming the local clock is correct, the peer has sent an invalid message.
2323
TooEarly,
24-
/// Light client finality update message does not match the locally constructed one.
25-
InvalidLightClientFinalityUpdate,
24+
/// Light client finalized update message does not match the locally constructed one, it has a
25+
/// different signature slot.
26+
MismatchedSignatureSlot { local: Slot, observed: Slot },
27+
/// Light client finalized update message does not match the locally constructed one, it has a
28+
/// different finalized block header for the same signature slot.
29+
MismatchedFinalizedHeader {
30+
local_finalized_header_root: Hash256,
31+
observed_finalized_header_root: Hash256,
32+
signature_slot: Slot,
33+
},
34+
/// Light client finalized update message does not match the locally constructed one, it has a
35+
/// different attested block header for the same signature slot and finalized header.
36+
MismatchedAttestedHeader {
37+
local_attested_header_root: Hash256,
38+
observed_attested_header_root: Hash256,
39+
finalized_header_root: Hash256,
40+
signature_slot: Slot,
41+
},
42+
/// Light client finalized update message does not match the locally constructed one, it has a
43+
/// different proof or sync aggregate for the same slot, attested header and finalized header.
44+
MismatchedProofOrSyncAggregate {
45+
attested_header_root: Hash256,
46+
finalized_header_root: Hash256,
47+
signature_slot: Slot,
48+
},
2649
/// Signature slot start time is none.
2750
SigSlotStartIsNone,
2851
/// Failed to construct a LightClientFinalityUpdate from state.
2952
FailedConstructingUpdate,
53+
/// Silently ignore this light client finality update
54+
Ignore,
3055
}
3156

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

85+
if let Some(latest_broadcasted_finality_update) = chain
86+
.light_client_server_cache
87+
.get_latest_broadcasted_finality_update()
88+
{
89+
// Ignore the incoming finality update if we've already broadcasted it
90+
if latest_broadcasted_finality_update == rcv_finality_update {
91+
return Err(Error::Ignore);
92+
}
93+
94+
// Ignore the incoming finality update if the latest broadcasted attested header slot
95+
// is greater than the incoming attested header slot.
96+
if latest_broadcasted_finality_update.get_attested_header_slot()
97+
> rcv_finality_update.get_attested_header_slot()
98+
{
99+
return Err(Error::Ignore);
100+
}
101+
}
102+
60103
let latest_finality_update = chain
61104
.light_client_server_cache
62105
.get_latest_finality_update()
63106
.ok_or(Error::FailedConstructingUpdate)?;
64107

65-
// verify that the gossiped finality update is the same as the locally constructed one.
108+
// Ignore the incoming finality update if the latest constructed attested header slot
109+
// is greater than the incoming attested header slot.
110+
if latest_finality_update.get_attested_header_slot()
111+
> rcv_finality_update.get_attested_header_slot()
112+
{
113+
return Err(Error::Ignore);
114+
}
115+
116+
// Verify that the gossiped finality update is the same as the locally constructed one.
66117
if latest_finality_update != rcv_finality_update {
67-
return Err(Error::InvalidLightClientFinalityUpdate);
118+
let signature_slot = latest_finality_update.signature_slot();
119+
if signature_slot != rcv_finality_update.signature_slot() {
120+
return Err(Error::MismatchedSignatureSlot {
121+
local: signature_slot,
122+
observed: rcv_finality_update.signature_slot(),
123+
});
124+
}
125+
let local_finalized_header_root = latest_finality_update.get_finalized_header_root();
126+
let observed_finalized_header_root = rcv_finality_update.get_finalized_header_root();
127+
if local_finalized_header_root != observed_finalized_header_root {
128+
return Err(Error::MismatchedFinalizedHeader {
129+
local_finalized_header_root,
130+
observed_finalized_header_root,
131+
signature_slot,
132+
});
133+
}
134+
let local_attested_header_root = latest_finality_update.get_attested_header_root();
135+
let observed_attested_header_root = rcv_finality_update.get_attested_header_root();
136+
if local_attested_header_root != observed_attested_header_root {
137+
return Err(Error::MismatchedAttestedHeader {
138+
local_attested_header_root,
139+
observed_attested_header_root,
140+
finalized_header_root: local_finalized_header_root,
141+
signature_slot,
142+
});
143+
}
144+
return Err(Error::MismatchedProofOrSyncAggregate {
145+
attested_header_root: local_attested_header_root,
146+
finalized_header_root: local_finalized_header_root,
147+
signature_slot,
148+
});
68149
}
69150

151+
chain
152+
.light_client_server_cache
153+
.set_latest_broadcasted_finality_update(rcv_finality_update.clone());
154+
70155
Ok(Self {
71156
light_client_finality_update: rcv_finality_update,
72157
seen_timestamp,

beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use eth2::types::Hash256;
44
use slot_clock::SlotClock;
55
use std::time::Duration;
66
use strum::AsRefStr;
7-
use types::LightClientOptimisticUpdate;
7+
use types::{LightClientOptimisticUpdate, Slot};
88

99
/// Returned when a light client optimistic update was not successfully verified. It might not have been verified for
1010
/// two reasons:
@@ -22,14 +22,30 @@ pub enum Error {
2222
///
2323
/// Assuming the local clock is correct, the peer has sent an invalid message.
2424
TooEarly,
25-
/// Light client optimistic update message does not match the locally constructed one.
26-
InvalidLightClientOptimisticUpdate,
25+
/// Light client optimistic update message does not match the locally constructed one, it has a
26+
/// different signature slot.
27+
MismatchedSignatureSlot { local: Slot, observed: Slot },
28+
/// Light client optimistic update message does not match the locally constructed one, it has a
29+
/// different block header at the same slot.
30+
MismatchedAttestedHeader {
31+
local_attested_header_root: Hash256,
32+
observed_attested_header_root: Hash256,
33+
signature_slot: Slot,
34+
},
35+
/// Light client optimistic update message does not match the locally constructed one, it has a
36+
/// different sync aggregate for the same slot and attested header.
37+
MismatchedSyncAggregate {
38+
attested_header_root: Hash256,
39+
signature_slot: Slot,
40+
},
2741
/// Signature slot start time is none.
2842
SigSlotStartIsNone,
2943
/// Failed to construct a LightClientOptimisticUpdate from state.
3044
FailedConstructingUpdate,
3145
/// Unknown block with parent root.
3246
UnknownBlockParentRoot(Hash256),
47+
/// Silently ignore this light client optimistic update
48+
Ignore,
3349
}
3450

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

80+
if let Some(latest_broadcasted_optimistic_update) = chain
81+
.light_client_server_cache
82+
.get_latest_broadcasted_optimistic_update()
83+
{
84+
// Ignore the incoming optimistic update if we've already broadcasted it
85+
if latest_broadcasted_optimistic_update == rcv_optimistic_update {
86+
return Err(Error::Ignore);
87+
}
88+
89+
// Ignore the incoming optimistic update if the latest broadcasted slot
90+
// is greater than the incoming slot.
91+
if latest_broadcasted_optimistic_update.get_slot() > rcv_optimistic_update.get_slot() {
92+
return Err(Error::Ignore);
93+
}
94+
}
95+
6496
let head = chain.canonical_head.cached_head();
6597
let head_block = &head.snapshot.beacon_block;
6698
// check if we can process the optimistic update immediately
@@ -76,11 +108,40 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
76108
.get_latest_optimistic_update()
77109
.ok_or(Error::FailedConstructingUpdate)?;
78110

79-
// verify that the gossiped optimistic update is the same as the locally constructed one.
111+
// Ignore the incoming optimistic update if the latest constructed slot
112+
// is greater than the incoming slot.
113+
if latest_optimistic_update.get_slot() > rcv_optimistic_update.get_slot() {
114+
return Err(Error::Ignore);
115+
}
116+
117+
// Verify that the gossiped optimistic update is the same as the locally constructed one.
80118
if latest_optimistic_update != rcv_optimistic_update {
81-
return Err(Error::InvalidLightClientOptimisticUpdate);
119+
let signature_slot = latest_optimistic_update.signature_slot();
120+
if signature_slot != rcv_optimistic_update.signature_slot() {
121+
return Err(Error::MismatchedSignatureSlot {
122+
local: signature_slot,
123+
observed: rcv_optimistic_update.signature_slot(),
124+
});
125+
}
126+
let local_attested_header_root = latest_optimistic_update.get_canonical_root();
127+
let observed_attested_header_root = rcv_optimistic_update.get_canonical_root();
128+
if local_attested_header_root != observed_attested_header_root {
129+
return Err(Error::MismatchedAttestedHeader {
130+
local_attested_header_root,
131+
observed_attested_header_root,
132+
signature_slot,
133+
});
134+
}
135+
return Err(Error::MismatchedSyncAggregate {
136+
attested_header_root: local_attested_header_root,
137+
signature_slot,
138+
});
82139
}
83140

141+
chain
142+
.light_client_server_cache
143+
.set_latest_broadcasted_optimistic_update(rcv_optimistic_update.clone());
144+
84145
let parent_root = rcv_optimistic_update.get_parent_root();
85146
Ok(Self {
86147
light_client_optimistic_update: rcv_optimistic_update,

beacon_node/beacon_chain/src/light_client_server_cache.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ pub struct LightClientServerCache<T: BeaconChainTypes> {
4040
latest_written_current_sync_committee: RwLock<Option<Arc<SyncCommittee<T::EthSpec>>>>,
4141
/// Caches state proofs by block root
4242
prev_block_cache: Mutex<lru::LruCache<Hash256, LightClientCachedData<T::EthSpec>>>,
43+
/// Tracks the latest broadcasted finality update
44+
latest_broadcasted_finality_update: RwLock<Option<LightClientFinalityUpdate<T::EthSpec>>>,
45+
/// Tracks the latest broadcasted optimistic update
46+
latest_broadcasted_optimistic_update: RwLock<Option<LightClientOptimisticUpdate<T::EthSpec>>>,
4347
}
4448

4549
impl<T: BeaconChainTypes> LightClientServerCache<T> {
@@ -49,6 +53,8 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
4953
latest_optimistic_update: None.into(),
5054
latest_light_client_update: None.into(),
5155
latest_written_current_sync_committee: None.into(),
56+
latest_broadcasted_finality_update: None.into(),
57+
latest_broadcasted_optimistic_update: None.into(),
5258
prev_block_cache: lru::LruCache::new(PREV_BLOCK_CACHE_SIZE).into(),
5359
}
5460
}
@@ -334,10 +340,89 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
334340
Ok(new_value)
335341
}
336342

343+
/// Checks if we've already broadcasted the latest finality update.
344+
/// If we haven't, update the `latest_broadcasted_finality_update` cache
345+
/// and return the latest finality update for broadcasting, else return `None`.
346+
pub fn should_broadcast_latest_finality_update(
347+
&self,
348+
) -> Option<LightClientFinalityUpdate<T::EthSpec>> {
349+
if let Some(latest_finality_update) = self.get_latest_finality_update() {
350+
let latest_broadcasted_finality_update = self.get_latest_broadcasted_finality_update();
351+
match latest_broadcasted_finality_update {
352+
Some(latest_broadcasted_finality_update) => {
353+
if latest_broadcasted_finality_update != latest_finality_update {
354+
self.set_latest_broadcasted_finality_update(latest_finality_update.clone());
355+
return Some(latest_finality_update);
356+
}
357+
}
358+
None => {
359+
self.set_latest_broadcasted_finality_update(latest_finality_update.clone());
360+
return Some(latest_finality_update);
361+
}
362+
}
363+
}
364+
365+
None
366+
}
367+
337368
pub fn get_latest_finality_update(&self) -> Option<LightClientFinalityUpdate<T::EthSpec>> {
338369
self.latest_finality_update.read().clone()
339370
}
340371

372+
pub fn get_latest_broadcasted_optimistic_update(
373+
&self,
374+
) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
375+
self.latest_broadcasted_optimistic_update.read().clone()
376+
}
377+
378+
pub fn get_latest_broadcasted_finality_update(
379+
&self,
380+
) -> Option<LightClientFinalityUpdate<T::EthSpec>> {
381+
self.latest_broadcasted_finality_update.read().clone()
382+
}
383+
384+
pub fn set_latest_broadcasted_optimistic_update(
385+
&self,
386+
optimistic_update: LightClientOptimisticUpdate<T::EthSpec>,
387+
) {
388+
*self.latest_broadcasted_optimistic_update.write() = Some(optimistic_update.clone());
389+
}
390+
391+
pub fn set_latest_broadcasted_finality_update(
392+
&self,
393+
finality_update: LightClientFinalityUpdate<T::EthSpec>,
394+
) {
395+
*self.latest_broadcasted_finality_update.write() = Some(finality_update.clone());
396+
}
397+
398+
/// Checks if we've already broadcasted the latest optimistic update.
399+
/// If we haven't, update the `latest_broadcasted_optimistic_update` cache
400+
/// and return the latest optimistic update for broadcasting, else return `None`.
401+
pub fn should_broadcast_latest_optimistic_update(
402+
&self,
403+
) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
404+
if let Some(latest_optimistic_update) = self.get_latest_optimistic_update() {
405+
let latest_broadcasted_optimistic_update =
406+
self.get_latest_broadcasted_optimistic_update();
407+
match latest_broadcasted_optimistic_update {
408+
Some(latest_broadcasted_optimistic_update) => {
409+
if latest_broadcasted_optimistic_update != latest_optimistic_update {
410+
self.set_latest_broadcasted_optimistic_update(
411+
latest_optimistic_update.clone(),
412+
);
413+
return Some(latest_optimistic_update);
414+
}
415+
}
416+
None => {
417+
self.set_latest_broadcasted_optimistic_update(latest_optimistic_update.clone());
418+
return Some(latest_optimistic_update);
419+
}
420+
}
421+
}
422+
423+
None
424+
}
425+
341426
pub fn get_latest_optimistic_update(&self) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
342427
self.latest_optimistic_update.read().clone()
343428
}

beacon_node/http_api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2600,7 +2600,7 @@ pub fn serve<T: BeaconChainTypes>(
26002600

26012601
let fork_name = chain
26022602
.spec
2603-
.fork_name_at_slot::<T::EthSpec>(*update.signature_slot());
2603+
.fork_name_at_slot::<T::EthSpec>(update.signature_slot());
26042604
match accept_header {
26052605
Some(api_types::Accept::Ssz) => Response::builder()
26062606
.status(200)

0 commit comments

Comments
 (0)