diff --git a/crates/blackbeard-api/src/bridgescan.rs b/crates/blackbeard-api/src/bridgescan.rs index acf9c4e..605e342 100644 --- a/crates/blackbeard-api/src/bridgescan.rs +++ b/crates/blackbeard-api/src/bridgescan.rs @@ -183,7 +183,28 @@ async fn mints( tokio::time::sleep(TIP_WAIT).await; continue; }; - let Ok(layout) = near.shard_layout(heights[0]).await else { + // From the first height in the batch that exists. NEAR skips heights, + // and asking at a skipped one is UNKNOWN_BLOCK forever: the backward + // pass sat on #218,897,633 that way, retrying the same never-produced + // block every turn without a word (blackbeard/observer#31, again). + let mut layout = None; + for &height in &heights { + match near.shard_layout(height).await { + Ok(l) => { + layout = Some(l); + break; + } + Err(e) if blackbeard_data::near::unknown(&e) => continue, + Err(e) => { + tracing::warn!(chain = %id, near_height = height, error = %e, + "bridge mint shard layout unreadable"); + break; + } + } + } + let Some(layout) = layout else { + tracing::warn!(chain = %id, from = heights[0], to = heights[heights.len() - 1], + "no height in the bridge mint batch is readable; will retry"); tokio::time::sleep(RETRY).await; continue; }; @@ -357,11 +378,20 @@ async fn history( tracing::info!(chain = %id, changes = changes.len(), "supply changes found before our nodes"); for (height, delta) in changes { + // A change can be recorded at a skipped height (the supply read falls + // back to the block before), so walk down past any that were never + // produced rather than asking about the same one forever. + let mut at = height; let layout = loop { tokio::time::sleep(ARCHIVE_SPACING).await; - match archive.shard_layout(height).await { + match archive.shard_layout(at).await { Ok(l) => break l, - Err(_) => tokio::time::sleep(RETRY).await, + Err(e) if blackbeard_data::near::unknown(&e) && at + LOOKBACK > height => at -= 1, + Err(e) => { + tracing::debug!(chain = %id, near_height = at, error = %e, + "archive layout unreadable; retrying"); + tokio::time::sleep(RETRY).await + } } }; let shard_of = |a: &str| core::shard_for(a, &layout.boundary_accounts, &layout.shard_ids);