1use crate::keys::{
14 blockidx_key, decode_boot, encode_boot, encode_slots, encode_value, slot_key, slot_prefix,
15};
16use crate::{
17 anchor_key, creation_in, settle_created, Archive, ArchiveError, BootState, Provenance, Result,
18 BLOCKIDX, BOOT, CREATED, META, PENDING, SLOTS, WATCH,
19};
20use alloy_primitives::{Address, B256};
21use bal_source::{BalSource, SourceError, SourcedBlock};
22use futures::future::join_all;
23use redb::ReadableTable;
24use std::collections::BTreeSet;
25use tracing::{debug, info};
26
27pub const FETCH_AHEAD: u64 = 8;
29
30#[derive(Debug, Clone, Default)]
32pub struct BackfillOpts {
33 pub to: Option<u64>,
36 pub max_blocks: Option<u64>,
40 pub resolve_only: bool,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum BackfillStop {
50 Target,
52 Creation(u64),
54 Resolved,
56 Budget,
58 PreBal(u64),
62 HistoryUnavailable(u64),
65 Nothing,
68}
69
70#[derive(Debug, Clone)]
72pub struct BackfillReport {
73 pub from: u64,
75 pub to: u64,
77 pub blocks_scanned: u64,
79 pub records_written: usize,
81 pub slots_resolved: usize,
83 pub unresolved: usize,
85 pub created_at: Option<u64>,
87 pub stopped: BackfillStop,
89}
90
91struct Guard<'a>(&'a Archive);
92
93impl Drop for Guard<'_> {
94 fn drop(&mut self) {
95 self.0.sync_idle();
96 }
97}
98
99struct Walker {
101 addr: Address,
102 start: u64,
104 target: u64,
105 known: Option<B256>,
108 joined: bool,
109 unresolved: BTreeSet<B256>,
110 report: BackfillReport,
111 done: bool,
112}
113
114impl Walker {
115 fn stop(&mut self, why: BackfillStop) {
116 if !self.done {
117 self.report.stopped = why;
118 self.done = true;
119 }
120 }
121}
122
123impl Archive {
124 pub async fn backfill<S: BalSource + ?Sized>(
132 &self,
133 source: &S,
134 addr: Address,
135 opts: BackfillOpts,
136 ) -> Result<BackfillReport> {
137 let mut reports = self.backfill_many(source, &[addr], opts).await?;
138 reports
139 .pop()
140 .ok_or(ArchiveError::Corrupt("backfill produced no report"))
141 }
142
143 pub async fn backfill_many<S: BalSource + ?Sized>(
150 &self,
151 source: &S,
152 addrs: &[Address],
153 opts: BackfillOpts,
154 ) -> Result<Vec<BackfillReport>> {
155 if !self.begin_sync() {
156 return Err(ArchiveError::SyncInProgress);
157 }
158 let _guard = Guard(self);
159 self.backfill_inner(source, addrs, opts).await
160 }
161
162 async fn backfill_inner<S: BalSource + ?Sized>(
163 &self,
164 source: &S,
165 addrs: &[Address],
166 opts: BackfillOpts,
167 ) -> Result<Vec<BackfillReport>> {
168 let head = self.head()?.map(|(h, _)| h).unwrap_or(0);
169 let mut walkers = Vec::with_capacity(addrs.len());
170 for &addr in addrs {
171 let start = self.start_of(addr)?.ok_or(ArchiveError::NotWatched(addr))?;
172 if head < start {
173 return Err(ArchiveError::HeadBelowStart { head, start });
174 }
175 let created = self.created_at(addr)?;
176 let unresolved = self.unknown_pre_values(addr)?;
177 let target = opts.to.unwrap_or(1).max(1);
178 let nothing = created.is_some()
179 || target >= start
180 || (opts.resolve_only && unresolved.is_empty());
181 let known = match self.header_at(start)? {
182 Some((h, _)) => Some(h),
183 None => self.anchor(addr)?,
184 };
185 walkers.push(Walker {
186 addr,
187 start,
188 target,
189 known,
190 joined: false,
191 report: BackfillReport {
192 from: start,
193 to: start,
194 blocks_scanned: 0,
195 records_written: 0,
196 slots_resolved: 0,
197 unresolved: unresolved.len(),
198 created_at: created,
199 stopped: BackfillStop::Nothing,
200 },
201 done: nothing,
202 unresolved,
203 });
204 }
205 let Some(top) = walkers.iter().filter(|w| !w.done).map(|w| w.start).max() else {
206 return Ok(walkers.into_iter().map(|w| w.report).collect());
207 };
208
209 let above = source.header(top).await?;
215 if above.number != top {
216 return Err(wrong_block(top, above.number));
217 }
218 let mut expect = above.parent_hash;
219 let mut last = above.hash;
220 let mut read = 0u64;
221
222 let mut cur = top - 1;
226 'walk: loop {
227 for w in walkers.iter_mut() {
229 if !w.done && w.target > cur {
230 w.stop(BackfillStop::Target);
231 }
232 }
233 let Some(lowest_target) = walkers.iter().filter(|w| !w.done).map(|w| w.target).min()
234 else {
235 break;
236 };
237 let mut batch = FETCH_AHEAD.min(cur - lowest_target + 1);
238 if let Some(m) = opts.max_blocks {
239 batch = batch.min(m.saturating_sub(read));
240 }
241 if batch == 0 {
242 for w in walkers.iter_mut() {
243 w.stop(BackfillStop::Budget);
244 }
245 break;
246 }
247 let numbers: Vec<u64> = (0..batch).map(|i| cur - i).collect();
248 let fetched = join_all(numbers.iter().map(|&b| source.block(b))).await;
249 for (b, res) in numbers.into_iter().zip(fetched) {
250 let blk = match res {
251 Ok(blk) => blk,
252 Err(SourceError::BlockNotFound(_)) | Err(SourceError::NoBal(_)) => {
253 for w in walkers.iter_mut() {
254 w.stop(BackfillStop::HistoryUnavailable(b));
255 }
256 break 'walk;
257 }
258 Err(e) => return Err(e.into()),
259 };
260 if blk.header.number != b {
261 return Err(wrong_block(b, blk.header.number));
262 }
263 if blk.header.hash != expect {
264 return Err(ArchiveError::InconsistentSource(b + 1));
265 }
266 let Some(bal_hash) = blk.header.block_access_list_hash else {
267 for w in walkers.iter_mut() {
268 w.stop(BackfillStop::PreBal(b));
269 }
270 break 'walk;
271 };
272 blk.bal
273 .verify(bal_hash)
274 .map_err(|err| ArchiveError::Verification { block: b, err })?;
275 read += 1;
276
277 for w in walkers.iter_mut() {
278 if w.done || w.start != b + 1 || w.target > b {
279 continue;
280 }
281 if !w.joined {
282 if let Some(k) = w.known {
283 if k != last {
284 return Err(ArchiveError::StartReplaced(w.start));
285 }
286 }
287 w.joined = true;
288 }
289 let (written, resolved, is_creation) =
290 self.backfill_block(w.addr, b, &blk, &mut w.unresolved)?;
291 w.start = b;
292 w.report.blocks_scanned += 1;
293 w.report.records_written += written;
294 w.report.slots_resolved += resolved;
295 w.report.to = b;
296 debug!(addr = %w.addr, block = b, written, "backfilled");
297 if is_creation {
298 w.report.created_at = Some(b);
299 w.unresolved.clear();
300 w.stop(BackfillStop::Creation(b));
301 } else if opts.resolve_only && w.unresolved.is_empty() {
302 w.stop(BackfillStop::Resolved);
303 }
304 }
305 expect = blk.header.parent_hash;
306 last = blk.header.hash;
307 cur = b.saturating_sub(1);
308 if walkers.iter().all(|w| w.done) || b == 0 {
309 break 'walk;
310 }
311 }
312 }
313 for w in walkers.iter_mut() {
314 w.report.unresolved = w.unresolved.len();
315 w.stop(BackfillStop::Target);
316 }
317 info!(walkers = walkers.len(), read, "backfill done");
318 Ok(walkers.into_iter().map(|w| w.report).collect())
319 }
320
321 fn unknown_pre_values(&self, addr: Address) -> Result<BTreeSet<B256>> {
323 let rtx = self.db.begin_read()?;
324 let boot = rtx.open_table(BOOT)?;
325 let mut out = BTreeSet::new();
326 for k in crate::collect_prefix_keys(&boot, addr.as_slice())? {
327 let state = boot.get(k.as_slice())?.and_then(|v| decode_boot(v.value()));
328 if matches!(
329 state,
330 Some(BootState::Pending { .. }) | Some(BootState::Lost { .. })
331 ) {
332 out.insert(B256::from_slice(&k[20..]));
333 }
334 }
335 Ok(out)
336 }
337
338 fn anchor(&self, addr: Address) -> Result<Option<B256>> {
339 let rtx = self.db.begin_read()?;
340 let meta = rtx.open_table(META)?;
341 Ok(meta
342 .get(anchor_key(addr).as_str())?
343 .and_then(|v| (v.value().len() == 32).then(|| B256::from_slice(v.value()))))
344 }
345
346 fn backfill_block(
350 &self,
351 addr: Address,
352 block: u64,
353 blk: &SourcedBlock,
354 unresolved: &mut BTreeSet<B256>,
355 ) -> Result<(usize, usize, bool)> {
356 let mut written = 0;
357 let mut resolved = 0;
358 let mut is_creation = false;
359 let txn = self.db.begin_write()?;
360 {
361 let mut watch = txn.open_table(WATCH)?;
362 if watch.get(addr.as_slice())?.map(|v| v.value()) != Some(block + 1) {
364 return Ok((0, 0, false));
365 }
366 if let Some(acc) = blk.bal.account(&addr) {
367 let mut slots = txn.open_table(SLOTS)?;
368 let mut idx = txn.open_table(BLOCKIDX)?;
369 let mut boot = txn.open_table(BOOT)?;
370 let mut pending = txn.open_table(PENDING)?;
371 let mut changed = Vec::with_capacity(acc.storage_changes.len());
372 for sc in &acc.storage_changes {
373 let slot = sc.slot_b256();
374 changed.push(slot);
375 if self.config.full_detail {
376 for ch in &sc.changes {
377 slots.insert(
378 slot_key(addr, slot, block, ch.block_access_index).as_slice(),
379 encode_value(Provenance::Bal, ch.value_b256()).as_slice(),
380 )?;
381 written += 1;
382 }
383 } else {
384 let ch = sc.final_change();
385 slots.insert(
386 slot_key(addr, slot, block, ch.block_access_index).as_slice(),
387 encode_value(Provenance::Bal, ch.value_b256()).as_slice(),
388 )?;
389 written += 1;
390 }
391 let key = slot_prefix(addr, slot);
394 let next = match boot
395 .get(key.as_slice())?
396 .and_then(|v| decode_boot(v.value()))
397 {
398 Some(BootState::Done) => None,
399 Some(BootState::Pending { .. }) => {
400 Some(BootState::Pending { first_seen: block })
401 }
402 Some(BootState::Lost { .. }) => Some(BootState::Lost { first_seen: block }),
403 None => Some(BootState::Pending { first_seen: block }),
404 };
405 if let Some(state) = next {
406 boot.insert(key.as_slice(), encode_boot(state).as_slice())?;
407 if matches!(state, BootState::Pending { .. }) {
408 pending.insert(key.as_slice(), block)?;
409 }
410 }
411 if unresolved.remove(&slot) {
412 resolved += 1;
413 }
414 }
415 if !changed.is_empty() {
416 idx.insert(
417 blockidx_key(addr, block).as_slice(),
418 encode_slots(&changed).as_slice(),
419 )?;
420 }
421 if creation_in(acc) {
422 txn.open_table(CREATED)?.insert(addr.as_slice(), block)?;
423 settle_created(&mut boot, &mut pending, addr)?;
424 is_creation = true;
425 }
426 }
427 watch.insert(addr.as_slice(), block)?;
428 txn.open_table(META)?
429 .insert(anchor_key(addr).as_str(), blk.header.hash.as_slice())?;
430 }
431 txn.commit()?;
432 Ok((written, resolved, is_creation))
433 }
434}
435
436fn wrong_block(asked: u64, got: u64) -> ArchiveError {
437 ArchiveError::Source(SourceError::Malformed(format!(
438 "asked for block {asked}, source answered with block {got}"
439 )))
440}