Skip to content

Commit c0a86bc

Browse files
committed
fix: prevent sync stuck on uncle blocks during chain reorgs
Signed-off-by: Eval EXEC <execvy@gmail.com>
1 parent aa31df1 commit c0a86bc

File tree

6 files changed

+128
-7
lines changed

6 files changed

+128
-7
lines changed

light-client-bin/src/subcmds.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ impl RunConfig {
4040
.expect("build consensus should be OK");
4141
storage.init_genesis_block(consensus.genesis_block().data());
4242

43+
// Cleanup any invalid matched blocks from previous runs (e.g., uncle blocks from chain reorgs)
44+
log::info!("Cleaning up invalid matched blocks...");
45+
storage.cleanup_invalid_matched_blocks();
46+
4347
let pending_txs = Arc::new(RwLock::new(PendingTxs::default()));
4448
let max_outbound_peers = self.run_env.network.max_outbound_peers;
4549
let network_state = NetworkState::from_config(self.run_env.network)

light-client-lib/src/protocols/light_client/mod.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,14 @@ impl LightClientProtocol {
385385
debug!("fork to number: {}", to_number);
386386
let mut matched_blocks = self.peers.matched_blocks().write().await;
387387
let mut start_number_opt = None;
388-
while let Some((start_number, _, _)) = self.storage.get_latest_matched_blocks()
388+
while let Some((start_number, blocks_count, _)) =
389+
self.storage.get_latest_matched_blocks()
389390
{
390-
if start_number > to_number {
391-
debug!("remove matched blocks start from: {}", start_number);
391+
// Remove matched blocks if the range overlaps or is after the fork point
392+
// The range is [start_number, start_number + blocks_count - 1]
393+
if start_number + blocks_count - 1 >= to_number {
394+
debug!("remove matched blocks start from: {} (range covers {} blocks, overlaps fork at {})",
395+
start_number, blocks_count, to_number);
392396
self.storage.remove_matched_blocks(start_number);
393397
} else {
394398
start_number_opt = Some(start_number);

light-client-lib/src/protocols/light_client/peers.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,11 +1489,25 @@ impl Peers {
14891489
matched_blocks
14901490
.iter()
14911491
.filter_map(|(key, value)| {
1492-
if !proof_requested_hashes.contains(key) && !value.0 {
1493-
Some(key.pack())
1494-
} else {
1495-
None
1492+
// Skip if already in a proof request
1493+
if proof_requested_hashes.contains(key) {
1494+
return None;
1495+
}
1496+
// Skip if already proved
1497+
if value.0 {
1498+
return None;
1499+
}
1500+
// Skip if marked as missing by peers (e.g., uncle blocks)
1501+
if let Some(fetch_info) = self.fetching_headers.get(&key.pack()) {
1502+
if fetch_info.missing {
1503+
log::warn!(
1504+
"Skipping matched block {:#x} - marked as missing by peers (likely an uncle block)",
1505+
key.pack()
1506+
);
1507+
return None;
1508+
}
14961509
}
1510+
Some(key.pack())
14971511
})
14981512
.take(limit)
14991513
.collect()

light-client-lib/src/storage/db/browser.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,54 @@ impl Storage {
11831183
self.put(key, &value)
11841184
.expect("db put matched blocks should be ok");
11851185
}
1186+
1187+
pub fn cleanup_invalid_matched_blocks(&self) {
1188+
use ckb_types::prelude::Unpack;
1189+
use log::warn;
1190+
1191+
let tip_number: u64 = self.get_tip_header().raw().number().unpack();
1192+
1193+
loop {
1194+
let entry = self.get_earliest_matched_blocks();
1195+
if entry.is_none() {
1196+
break;
1197+
}
1198+
1199+
let (start_number, blocks_count, block_hashes) = entry.unwrap();
1200+
let mut should_remove = false;
1201+
1202+
for (block_hash, _) in &block_hashes {
1203+
if let Some(header) = self.get_header(block_hash) {
1204+
let stored_number: u64 = header.number();
1205+
if stored_number < start_number || stored_number >= start_number + blocks_count
1206+
{
1207+
warn!(
1208+
"Invalid matched block {:#x} at number {} outside expected range [{}, {}), removing entry at start_number={}",
1209+
block_hash, stored_number, start_number, start_number + blocks_count, start_number
1210+
);
1211+
should_remove = true;
1212+
break;
1213+
}
1214+
} else {
1215+
if start_number + 1000 < tip_number {
1216+
warn!(
1217+
"Matched block {:#x} not found in storage, entry at start_number={} is {} blocks behind tip, removing",
1218+
block_hash, start_number, tip_number - start_number
1219+
);
1220+
should_remove = true;
1221+
break;
1222+
}
1223+
}
1224+
}
1225+
1226+
if should_remove {
1227+
self.remove_matched_blocks(start_number);
1228+
} else {
1229+
break;
1230+
}
1231+
}
1232+
}
1233+
11861234
pub fn add_fetched_header(&self, hwe: &HeaderWithExtension) {
11871235
let mut batch = self.batch();
11881236
let block_hash = hwe.header.calc_header_hash();

light-client-lib/src/storage/db/native.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,53 @@ impl Storage {
712712
.expect("db put matched blocks should be ok");
713713
}
714714

715+
pub fn cleanup_invalid_matched_blocks(&self) {
716+
use ckb_types::prelude::Unpack;
717+
use log::warn;
718+
719+
let tip_number: u64 = self.get_tip_header().raw().number().unpack();
720+
721+
loop {
722+
let entry = self.get_earliest_matched_blocks();
723+
if entry.is_none() {
724+
break;
725+
}
726+
727+
let (start_number, blocks_count, block_hashes) = entry.unwrap();
728+
let mut should_remove = false;
729+
730+
for (block_hash, _) in &block_hashes {
731+
if let Some(header) = self.get_header(block_hash) {
732+
let stored_number: u64 = header.number();
733+
if stored_number < start_number || stored_number >= start_number + blocks_count
734+
{
735+
warn!(
736+
"Invalid matched block {:#x} at number {} outside expected range [{}, {}), removing entry at start_number={}",
737+
block_hash, stored_number, start_number, start_number + blocks_count, start_number
738+
);
739+
should_remove = true;
740+
break;
741+
}
742+
} else {
743+
if start_number + 1000 < tip_number {
744+
warn!(
745+
"Matched block {:#x} not found in storage, entry at start_number={} is {} blocks behind tip, removing",
746+
block_hash, start_number, tip_number - start_number
747+
);
748+
should_remove = true;
749+
break;
750+
}
751+
}
752+
}
753+
754+
if should_remove {
755+
self.remove_matched_blocks(start_number);
756+
} else {
757+
break;
758+
}
759+
}
760+
}
761+
715762
pub fn add_fetched_header(&self, hwe: &HeaderWithExtension) {
716763
let mut batch = self.batch();
717764
let block_hash = hwe.header.calc_header_hash();

wasm/light-client-wasm/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ pub async fn light_client(
154154

155155
storage.init_genesis_block(genesis);
156156

157+
// Cleanup any invalid matched blocks from previous runs (e.g., uncle blocks from chain reorgs)
158+
log::info!("Cleaning up invalid matched blocks...");
159+
storage.cleanup_invalid_matched_blocks();
160+
157161
let pending_txs = Arc::new(tokio::sync::RwLock::new(PendingTxs::default()));
158162
let max_outbound_peers = config.network.max_outbound_peers;
159163
let network_secret_key =

0 commit comments

Comments
 (0)