fix(api): give NEAR backfills every other turn instead of none
ci / gate (push) Successful in 4m39s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m20s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 19s

The bridge's mint scan sat with its backfill stalled — 118,898 blocks
between gargravarr's oldest and where it started, unread and not moving
— because every NEAR scan went back only once it was level with the tip.
A forward pass takes longer than NEAR's 0.6 s block, so a live scan is
always a block or two behind and the backward turn never came. #44's
example deposit was waiting on exactly that stretch.

The four NEAR scans (bridge mints, intents, intents flows, pool calls) now
alternate while history remains. Forward runs at half pace only while a
backfill is pending, and every scan here keeps up with the tip at that.

Refs #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
This commit is contained in:
2026-10-07 10:03:46 +03:00
co-authored by Claude Opus 5.5
parent 084fd5ba2c
commit 5f30000539
4 changed files with 57 additions and 30 deletions
+18 -11
View File
@@ -143,6 +143,7 @@ async fn mints(
) {
let id = chain.id();
let key = mint_cursor(&intents.asset);
let mut backward_turn = false;
loop {
if chain.read().current_runtime().is_none() {
tokio::time::sleep(RETRY).await;
@@ -165,17 +166,23 @@ async fn mints(
}
};
let (low, high) = cursor.unwrap_or((tip, tip.saturating_sub(1)));
let (heights, descending): (Vec<u64>, bool) = if high < tip {
((high + 1..=tip.min(high + BATCH)).collect(), false)
} else if low > oldest {
(
(low.saturating_sub(BATCH).max(oldest)..low).rev().collect(),
true,
)
} else {
tokio::time::sleep(TIP_WAIT).await;
continue;
};
// Every other pass goes back while there is history left: forward passes
// take longer than NEAR's 0.6 s blocks, so a scan that waited to be
// level with the tip before going back never went back at all — the
// bridge's mint backfill sat with 118,898 blocks unread (2026-10-07).
backward_turn = !backward_turn;
let (heights, descending): (Vec<u64>, bool) =
if high < tip && !(backward_turn && low > oldest) {
((high + 1..=tip.min(high + BATCH)).collect(), false)
} else if low > oldest {
(
(low.saturating_sub(BATCH).max(oldest)..low).rev().collect(),
true,
)
} else {
tokio::time::sleep(TIP_WAIT).await;
continue;
};
let Ok(layout) = near.shard_layout(heights[0]).await else {
tokio::time::sleep(RETRY).await;
continue;
+16 -9
View File
@@ -133,6 +133,7 @@ pub fn spawn(chain: Arc<ChainRuntime>, store: Store) {
/// Walk NEAR blocks, recording trades.
async fn scan(chain: Arc<ChainRuntime>, store: Store, near: NearClient, config: IntentsConfig) {
let id = chain.id();
let mut backward_turn = false;
loop {
// The cursor references the chain row, which the chain's own poll
// writes once its node has answered.
@@ -161,15 +162,21 @@ async fn scan(chain: Arc<ChainRuntime>, store: Store, near: NearClient, config:
// than what traded last week, and both arrive in the end.
let (low, high) = scan.unwrap_or((tip, tip.saturating_sub(1)));
let (heights, descending): (Vec<u64>, bool) = if high < tip {
((high + 1..=tip.min(high + BATCH)).collect(), false)
} else if low > config.start_height {
let floor = low.saturating_sub(BATCH).max(config.start_height);
((floor..low).rev().collect(), true)
} else {
tokio::time::sleep(TIP_WAIT).await;
continue;
};
// Every other pass goes back while there is history left: forward passes
// take longer than NEAR's 0.6 s blocks, so a scan that waited to be
// level with the tip before going back never went back at all — the
// bridge's mint backfill sat with 118,898 blocks unread (2026-10-07).
backward_turn = !backward_turn;
let (heights, descending): (Vec<u64>, bool) =
if high < tip && !(backward_turn && low > config.start_height) {
((high + 1..=tip.min(high + BATCH)).collect(), false)
} else if low > config.start_height {
let floor = low.saturating_sub(BATCH).max(config.start_height);
((floor..low).rev().collect(), true)
} else {
tokio::time::sleep(TIP_WAIT).await;
continue;
};
// Per batch, not once: resharding renumbers shards, and a layout read
// at startup would follow the wrong one after it while every call
+7 -1
View File
@@ -141,6 +141,7 @@ async fn scan(chain: Arc<ChainRuntime>, store: Store, near: NearClient, config:
let token = config.token_contract().unwrap_or_default().to_owned();
let holder = config.contract.clone();
tracing::info!(chain = %id, %token, %holder, "following the asset into and out of NEAR Intents");
let mut backward_turn = false;
loop {
if chain.read().current_runtime().is_none() {
tokio::time::sleep(RETRY).await;
@@ -166,7 +167,12 @@ async fn scan(chain: Arc<ChainRuntime>, store: Store, near: NearClient, config:
// and the first block above it has no "before" to compare with.
let floor = oldest.max(config.start_height) + 1;
let (low, high) = cursor.unwrap_or((tip, tip.saturating_sub(1)));
let (from, to, descending) = if high < tip {
// Every other pass goes back while there is history left: forward passes
// take longer than NEAR's 0.6 s blocks, so a scan that waited to be
// level with the tip before going back never went back at all — the
// bridge's mint backfill sat with 118,898 blocks unread (2026-10-07).
backward_turn = !backward_turn;
let (from, to, descending) = if high < tip && !(backward_turn && low > floor) {
(high + 1, tip.min(high + BATCH), false)
} else if low > floor {
(low.saturating_sub(BATCH).max(floor), low - 1, true)
+16 -9
View File
@@ -104,6 +104,7 @@ async fn scan(chain: Arc<ChainRuntime>, store: Store, near: NearClient, config:
let key = cursor_key(&config.asset);
let pools = crate::refdcl::contracts(&config);
let asset_contract = config.token_contract().unwrap_or_default().to_owned();
let mut backward_turn = false;
loop {
if chain.read().current_runtime().is_none() {
tokio::time::sleep(RETRY).await;
@@ -127,15 +128,21 @@ async fn scan(chain: Arc<ChainRuntime>, store: Store, near: NearClient, config:
};
let (low, high) = cursor.unwrap_or((tip, tip.saturating_sub(1)));
let floor = oldest.max(config.start_height);
let (heights, descending): (Vec<u64>, bool) = if high < tip {
((high + 1..=tip.min(high + BATCH)).collect(), false)
} else if low > floor {
let from = low.saturating_sub(BATCH).max(floor);
((from..low).rev().collect(), true)
} else {
tokio::time::sleep(TIP_WAIT).await;
continue;
};
// Every other pass goes back while there is history left: forward passes
// take longer than NEAR's 0.6 s blocks, so a scan that waited to be
// level with the tip before going back never went back at all — the
// bridge's mint backfill sat with 118,898 blocks unread (2026-10-07).
backward_turn = !backward_turn;
let (heights, descending): (Vec<u64>, bool) =
if high < tip && !(backward_turn && low > floor) {
((high + 1..=tip.min(high + BATCH)).collect(), false)
} else if low > floor {
let from = low.saturating_sub(BATCH).max(floor);
((from..low).rev().collect(), true)
} else {
tokio::time::sleep(TIP_WAIT).await;
continue;
};
let mut layout = None;
for &height in &heights {