Skip to content

Commit 38cfde4

Browse files
authored
fix(lean): reject forward-sync blocks ahead of wall-clock to stop on_tick freeze (#1523)
1 parent 0084915 commit 38cfde4

2 files changed

Lines changed: 131 additions & 4 deletions

File tree

crates/common/chain/lean/src/service.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,26 @@ impl LeanChainService {
20482048
);
20492049
}
20502050
}
2051+
ForwardSyncResults::BlockAheadOfWallClock {
2052+
previous_queue,
2053+
bad_root,
2054+
bad_slot,
2055+
wall_clock_slot,
2056+
} => {
2057+
let removed_pending_block = self.remove_pending_block(bad_root).await?;
2058+
self.backfill_state
2059+
.remove_processed_queue(previous_queue.starting_root);
2060+
self.dropped_backfill_roots.insert(bad_root);
2061+
warn!(
2062+
starting_root = ?previous_queue.starting_root,
2063+
starting_slot = previous_queue.starting_slot,
2064+
bad_root = ?bad_root,
2065+
bad_slot,
2066+
wall_clock_slot,
2067+
removed_pending_block,
2068+
"Forward background sync block slot ahead of wall clock; purged bad pending block and suppressing the root",
2069+
);
2070+
}
20512071
}
20522072

20532073
Ok(())

crates/common/chain/lean/src/sync/forward_background_syncer.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ream_network_state_lean::NetworkState;
99
use ream_storage::tables::{field::REDBField, table::REDBTable};
1010
use tree_hash::TreeHash;
1111

12-
use crate::sync::job::queue::JobQueue;
12+
use crate::{slot::get_current_slot, sync::job::queue::JobQueue};
1313

1414
pub struct ForwardBackgroundSyncer {
1515
pub store: Arc<LeanStoreWriter>,
@@ -107,9 +107,23 @@ impl ForwardBackgroundSyncer {
107107
"Failed to find block with root {root:?} in pending blocks during insertion"
108108
)
109109
})?;
110-
let time = lean_network_spec().genesis_time
111-
+ (block.block.slot * lean_network_spec().seconds_per_slot);
112110
let block_slot = block.block.slot;
111+
112+
// Reject blocks that are ahead of wall-clock by more than one slot. This
113+
// is a safety check to prevent importing blocks from malicious peers.
114+
let wall_clock_slot = get_current_slot();
115+
if block_slot > wall_clock_slot + 1 {
116+
return Ok(ForwardSyncResults::BlockAheadOfWallClock {
117+
previous_queue: self.job_queue.clone(),
118+
bad_root: root,
119+
bad_slot: block_slot,
120+
wall_clock_slot,
121+
});
122+
}
123+
124+
let spec = lean_network_spec();
125+
let time = spec.genesis_time + block_slot * spec.seconds_per_slot;
126+
113127
store_writer.on_tick(time, false, false).await?;
114128
store_writer.on_block(&block, true).await?;
115129
blocks_synced += 1;
@@ -155,6 +169,12 @@ pub enum ForwardSyncResults {
155169
actual_root: B256,
156170
network_finalized_slot: u64,
157171
},
172+
BlockAheadOfWallClock {
173+
previous_queue: JobQueue,
174+
bad_root: B256,
175+
bad_slot: u64,
176+
wall_clock_slot: u64,
177+
},
158178
}
159179

160180
#[cfg(test)]
@@ -164,7 +184,7 @@ mod tests {
164184
use libp2p_identity::PeerId;
165185
#[cfg(feature = "devnet5")]
166186
use ream_consensus_lean::attestation::MultiMessageAggregate;
167-
use ream_consensus_lean::block::SignedBlock;
187+
use ream_consensus_lean::block::{Block, SignedBlock};
168188
use ream_fork_choice_lean::store::Store;
169189
use ream_peer::{ConnectionState, Direction};
170190
use ream_sync::rwlock::Writer;
@@ -373,4 +393,91 @@ mod tests {
373393
other => panic!("expected completed result, got {other:?}"),
374394
}
375395
}
396+
397+
async fn run_crafted_high_slot_sync(
398+
crafted_slot: u64,
399+
) -> (anyhow::Result<ForwardSyncResults>, u64) {
400+
let store = sample_store(10).await;
401+
402+
let (head_root, genesis_block) = {
403+
let db = store.store.lock().await;
404+
let head_root = db.head_provider().get().unwrap();
405+
let genesis_block = db.block_provider().get(head_root).unwrap().unwrap();
406+
(head_root, genesis_block)
407+
};
408+
409+
let crafted_block = SignedBlock {
410+
block: Block {
411+
slot: crafted_slot,
412+
proposer_index: 0,
413+
parent_root: head_root,
414+
state_root: B256::ZERO,
415+
body: genesis_block.block.body.clone(),
416+
},
417+
#[cfg(feature = "devnet5")]
418+
proof: MultiMessageAggregate {
419+
proof: ssz_types::VariableList::default(),
420+
},
421+
};
422+
let crafted_root = crafted_block.block.tree_hash_root();
423+
424+
store
425+
.store
426+
.lock()
427+
.await
428+
.pending_blocks_provider()
429+
.insert(crafted_root, crafted_block)
430+
.unwrap();
431+
432+
let (writer, _reader) = Writer::new(store);
433+
let writer = Arc::new(writer);
434+
let network_state = writer.read().await.network_state.clone();
435+
let mut queue = JobQueue::new(crafted_root, crafted_slot, crafted_slot);
436+
queue.is_complete = true;
437+
438+
let mut syncer = ForwardBackgroundSyncer::new(writer.clone(), network_state, queue);
439+
let result = syncer.start().await;
440+
let final_clock = writer
441+
.read()
442+
.await
443+
.store
444+
.lock()
445+
.await
446+
.time_provider()
447+
.get()
448+
.unwrap();
449+
(result, final_clock)
450+
}
451+
452+
#[tokio::test]
453+
async fn test_b2_high_slot_block_does_not_freeze_or_poison_clock() {
454+
let (result, final_clock) = run_crafted_high_slot_sync(1_000_000_000_000).await;
455+
456+
match result.unwrap() {
457+
ForwardSyncResults::BlockAheadOfWallClock { bad_slot, .. } => {
458+
assert_eq!(bad_slot, 1_000_000_000_000);
459+
}
460+
other => panic!("expected BlockAheadOfWallClock, got {other:?}"),
461+
}
462+
assert!(
463+
final_clock < 1_000,
464+
"clock must not be poisoned toward the crafted slot"
465+
);
466+
}
467+
468+
#[tokio::test]
469+
async fn test_b2_near_max_slot_overflow_is_defended() {
470+
let (result, final_clock) = run_crafted_high_slot_sync(u64::MAX - 5).await;
471+
472+
match result.unwrap() {
473+
ForwardSyncResults::BlockAheadOfWallClock { bad_slot, .. } => {
474+
assert_eq!(bad_slot, u64::MAX - 5);
475+
}
476+
other => panic!("expected BlockAheadOfWallClock, got {other:?}"),
477+
}
478+
assert!(
479+
final_clock < 1_000,
480+
"clock must not be poisoned toward the crafted slot"
481+
);
482+
}
376483
}

0 commit comments

Comments
 (0)