Skip to main content

bal_archive/
backfill.rs

1//! Backfill: extend an address's history *backwards* from its watch start by
2//! reading older blocks' BALs. No proofs involved — a BAL is committed to by
3//! its own header, and headers are chained by `parent_hash` up to the block
4//! the archive already holds, so every record written here is verified
5//! exactly like one written by the forward sync.
6//!
7//! Walking back answers "what was in this slot before its first recorded
8//! change": the last write before it. Reaching the contract's creation
9//! answers it for every slot at once (no storage before creation, EIP-7610).
10//! Neither needs `eth_getProof`, so neither is bounded by the node's state
11//! window; the only bound is how far back the node still serves blocks.
12
13use 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
27/// Blocks requested concurrently per round. Verification stays sequential.
28pub const FETCH_AHEAD: u64 = 8;
29
30/// What to walk back to.
31#[derive(Debug, Clone, Default)]
32pub struct BackfillOpts {
33    /// Lowest block to read (inclusive). `None`: as far as the node serves,
34    /// or the contract's creation, whichever comes first.
35    pub to: Option<u64>,
36    /// Stop after this many blocks; the caller loops and reports progress.
37    /// Each call commits what it read, so a stopped backfill resumes where it
38    /// left off.
39    pub max_blocks: Option<u64>,
40    /// Stop as soon as every slot that currently has an unknown pre-value
41    /// (pending or lost) has found its last earlier write. Slots that never
42    /// changed since the start are not waited for — only creation settles
43    /// those.
44    pub resolve_only: bool,
45}
46
47/// Why a backfill call returned.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum BackfillStop {
50    /// Reached `opts.to` (or block 1).
51    Target,
52    /// The contract's creation was seen at this block. History is complete.
53    Creation(u64),
54    /// `resolve_only` and every unknown pre-value has been found.
55    Resolved,
56    /// `max_blocks` read; call again to continue.
57    Budget,
58    /// This block's header carries no BAL hash: history before the BAL fork
59    /// cannot be read from blocks. Proofs against an archive are the only
60    /// way further back.
61    PreBal(u64),
62    /// The node does not serve this block (history expiry, or a pruned
63    /// backup). A source that still has old blocks can continue from here.
64    HistoryUnavailable(u64),
65    /// Nothing to do: the address is already known to be created, or the
66    /// start is already at the target.
67    Nothing,
68}
69
70/// What one [`Archive::backfill`] call did.
71#[derive(Debug, Clone)]
72pub struct BackfillReport {
73    /// Watch start before this call.
74    pub from: u64,
75    /// Watch start after this call (the lowest block now covered).
76    pub to: u64,
77    /// Blocks read and verified.
78    pub blocks_scanned: u64,
79    /// Slot records written.
80    pub records_written: usize,
81    /// Slots whose previously unknown pre-value was found in this call.
82    pub slots_resolved: usize,
83    /// Slots whose pre-value is still unknown after this call.
84    pub unresolved: usize,
85    /// Creation block, if known after this call.
86    pub created_at: Option<u64>,
87    /// Why the call returned.
88    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
99/// One address walking backwards inside [`Archive::backfill_many`].
100struct Walker {
101    addr: Address,
102    /// Current watch start; moves down one block per applied block.
103    start: u64,
104    target: u64,
105    /// Hash the archive holds for `start` (stored header or backfill anchor);
106    /// checked against the chain when the walker joins.
107    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    /// Extend `addr`'s history backwards from its watch start. Takes the
125    /// sync slot (a concurrent `sync` is refused and vice versa); reads stay
126    /// available throughout. Every block is verified: header chained to the
127    /// one above it, BAL hashed against the header.
128    ///
129    /// The archive must have synced to at least the watch start, so that the
130    /// block above the first one read is one the archive holds.
131    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    /// [`Archive::backfill`] for several addresses in **one** backward walk:
144    /// every block is fetched and verified once and applied to each address
145    /// whose history has not reached it yet, so a protocol of N contracts
146    /// costs the same RPC traffic as one. Each address keeps its own start,
147    /// target, creation and report; the reports come back in input order.
148    /// `max_blocks` bounds the blocks read by the walk as a whole.
149    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        // The block above the first one read must be the block the archive
210        // holds — by stored hash near the head, by the backfill anchor below
211        // it. Otherwise that start was reorged and the forward sync has to
212        // sort it out first. Every walker is checked the same way when its
213        // start block is reached (`last` is then the hash of that block).
214        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        // Blocks are fetched [`FETCH_AHEAD`] at a time (the network round
223        // trip dominates on a remote node) and verified strictly in order:
224        // each header must be the parent of the one above it.
225        let mut cur = top - 1;
226        'walk: loop {
227            // Walkers whose target is above `cur` are finished.
228            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    /// Slots of `addr` whose pre-value is pending or lost.
322    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    /// Write one older block for `addr` and move its start down to `block`,
347    /// in one transaction. Returns `(records written, pre-values resolved,
348    /// creation seen)`.
349    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            // Unwatched while we were fetching: write nothing.
363            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                    // The slot's earliest known change is now this block; what
392                    // is unknown moved below it.
393                    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}