Skip to main content

bal_archive/
writes.rs

1//! Writes used by the sync and backfill loops: bootstrap records, rollback,
2//! applying one block in one transaction.
3
4use super::*;
5
6impl Archive {
7    /// Store pre-values proven at `proof_block` as records at `start - 1`,
8    /// marking the slots `Done`. A value is written only if it is actually
9    /// the pre-value: the slot is unseen, or its first recorded change is
10    /// *after* `proof_block`. Anything else (already `Done`, or a proof taken
11    /// at or after the first change) is skipped, so a racing sync or a node
12    /// answering for the wrong slots cannot plant a post-value as a
13    /// pre-value. Returns how many were written.
14    pub(crate) fn put_bootstrap(
15        &self,
16        addr: Address,
17        start: u64,
18        proof_block: u64,
19        proof_block_hash: B256,
20        values: &[(B256, B256)],
21    ) -> Result<usize> {
22        let mut written = 0;
23        let txn = self.db.begin_write()?;
24        {
25            // The proof was taken against a watch and a block. If either
26            // changed while it was in flight (unwatch + watch, a reorg that
27            // replaced the block), the values describe nothing we hold.
28            let watch = txn.open_table(WATCH)?;
29            if watch.get(addr.as_slice())?.map(|v| v.value()) != Some(start) {
30                return Ok(0);
31            }
32            let hashes = txn.open_table(HASHES)?;
33            let same_block = hashes
34                .get(proof_block)?
35                .map(|v| v.value().len() == 64 && v.value()[..32] == proof_block_hash[..])
36                .unwrap_or(false);
37            if !same_block {
38                return Ok(0);
39            }
40            let mut slots = txn.open_table(SLOTS)?;
41            let mut boot = txn.open_table(BOOT)?;
42            let mut pending = txn.open_table(PENDING)?;
43            for (slot, value) in values {
44                let key = slot_prefix(addr, *slot);
45                let ok = match boot
46                    .get(key.as_slice())?
47                    .and_then(|v| decode_boot(v.value()))
48                {
49                    None => true,
50                    Some(BootState::Pending { first_seen })
51                    | Some(BootState::Lost { first_seen }) => first_seen > proof_block,
52                    Some(BootState::Done) => false,
53                };
54                if !ok {
55                    continue;
56                }
57                slots.insert(
58                    slot_key(addr, *slot, start - 1, u32::MAX).as_slice(),
59                    encode_value(Provenance::Proof, *value).as_slice(),
60                )?;
61                boot.insert(key.as_slice(), encode_boot(BootState::Done).as_slice())?;
62                pending.remove(key.as_slice())?;
63                written += 1;
64            }
65        }
66        txn.commit()?;
67        Ok(written)
68    }
69
70    pub(crate) fn mark_lost(&self, addr: Address, slots: &[B256], first_seen: u64) -> Result<()> {
71        let txn = self.db.begin_write()?;
72        {
73            if txn.open_table(WATCH)?.get(addr.as_slice())?.is_none() {
74                return Ok(()); // unwatched meanwhile; nothing to mark
75            }
76            let mut boot = txn.open_table(BOOT)?;
77            let mut pending = txn.open_table(PENDING)?;
78            for slot in slots {
79                let key = slot_prefix(addr, *slot);
80                boot.insert(
81                    key.as_slice(),
82                    encode_boot(BootState::Lost { first_seen }).as_slice(),
83                )?;
84                pending.remove(key.as_slice())?;
85            }
86        }
87        txn.commit()?;
88        Ok(())
89    }
90
91    /// All slots whose bootstrap is pending: `(addr, slot, first_seen)`.
92    /// Reads the pending index only.
93    pub(crate) fn pending_bootstraps(&self) -> Result<Vec<(Address, B256, u64)>> {
94        let rtx = self.db.begin_read()?;
95        let t = rtx.open_table(PENDING)?;
96        let mut out = Vec::new();
97        for item in t.iter()? {
98            let (k, v) = item?;
99            let k = k.value();
100            if k.len() != SLOT_PREFIX_LEN {
101                return Err(ArchiveError::Corrupt("pending key"));
102            }
103            out.push((
104                Address::from_slice(&k[..20]),
105                B256::from_slice(&k[20..]),
106                v.value(),
107            ));
108        }
109        Ok(out)
110    }
111
112    /// Delete everything above `block` and reset head to it. Walks the block
113    /// index per watched address, so the cost is proportional to the records
114    /// being removed, not to the size of the archive.
115    ///
116    /// If the fork is below an address's `start - 1`, every proven pre-value
117    /// of that address is dropped too: those proofs were taken on a branch
118    /// that may have written the slot before `start`, so they no longer
119    /// describe the canonical chain. They are re-proven when needed.
120    pub fn rollback_to(&self, block: u64) -> Result<()> {
121        let (hash, _) = self
122            .header_at(block)?
123            .ok_or(ArchiveError::ReorgBeyondHorizon(block))?;
124        let watches = self.watchlist()?;
125        let txn = self.db.begin_write()?;
126        {
127            let mut slots = txn.open_table(SLOTS)?;
128            let mut idx = txn.open_table(BLOCKIDX)?;
129            let mut boot = txn.open_table(BOOT)?;
130            let mut pending = txn.open_table(PENDING)?;
131            for (addr, start) in &watches {
132                if block + 1 < *start {
133                    // Fork below start - 1: nothing of this address survives.
134                    for k in collect_prefix_keys(&slots, addr.as_slice())? {
135                        slots.remove(k.as_slice())?;
136                    }
137                    for k in collect_prefix_keys(&idx, addr.as_slice())? {
138                        idx.remove(k.as_slice())?;
139                    }
140                    for k in collect_prefix_keys(&boot, addr.as_slice())? {
141                        boot.remove(k.as_slice())?;
142                    }
143                    for k in collect_prefix_keys(&pending, addr.as_slice())? {
144                        pending.remove(k.as_slice())?;
145                    }
146                    continue;
147                }
148                let lo = blockidx_key(*addr, block + 1);
149                let hi = prefix_end(addr.as_slice());
150                let mut victims = Vec::new();
151                for item in idx.range::<&[u8]>(bounds(&lo, hi.as_deref()))? {
152                    let (k, v) = item?;
153                    victims.push((k.value().to_vec(), v.value().to_vec()));
154                }
155                for (k, v) in victims {
156                    let (_, b) = parse_blockidx_key(&k).ok_or(ArchiveError::Corrupt("blockidx"))?;
157                    let written =
158                        decode_slots(&v).ok_or(ArchiveError::Corrupt("blockidx value"))?;
159                    for slot in written {
160                        let sl = slot_key(*addr, slot, b, 0);
161                        let sh = slot_key(*addr, slot, b, u32::MAX);
162                        let ks: Vec<Vec<u8>> = slots
163                            .range::<&[u8]>(sl.as_slice()..=sh.as_slice())?
164                            .map(|r| r.map(|(k, _)| k.value().to_vec()))
165                            .collect::<std::result::Result<_, _>>()?;
166                        for sk in ks {
167                            slots.remove(sk.as_slice())?;
168                        }
169                        // A slot first seen above the fork was never seen on
170                        // the canonical chain: forget its pending/lost state.
171                        let bk = slot_prefix(*addr, slot);
172                        let forget = match boot
173                            .get(bk.as_slice())?
174                            .and_then(|v| decode_boot(v.value()))
175                        {
176                            Some(BootState::Pending { first_seen })
177                            | Some(BootState::Lost { first_seen }) => first_seen > block,
178                            _ => false,
179                        };
180                        if forget {
181                            boot.remove(bk.as_slice())?;
182                            pending.remove(bk.as_slice())?;
183                        }
184                    }
185                    idx.remove(k.as_slice())?;
186                }
187            }
188            let mut hashes = txn.open_table(HASHES)?;
189            let above: Vec<u64> = hashes
190                .range(block + 1..)?
191                .map(|r| r.map(|(k, _)| k.value()))
192                .collect::<std::result::Result<_, _>>()?;
193            for b in above {
194                hashes.remove(b)?;
195            }
196            // A creation seen above the fork was seen on a dead branch.
197            let mut created = txn.open_table(CREATED)?;
198            let stale: Vec<Vec<u8>> = created
199                .iter()?
200                .filter_map(|r| r.ok())
201                .filter(|(_, v)| v.value() > block)
202                .map(|(k, _)| k.value().to_vec())
203                .collect();
204            for k in stale {
205                created.remove(k.as_slice())?;
206            }
207            let mut meta = txn.open_table(META)?;
208            meta.insert(META_HEAD, head_bytes(block, hash).as_slice())?;
209        }
210        txn.commit()?;
211        Ok(())
212    }
213
214    /// Apply one block in a single transaction. `verified` says whether the
215    /// BAL matched the header (false only under `allow_unverified`). Returns
216    /// the slots that appeared for the first time (candidates for early
217    /// bootstrap), grouped by address, and the number of slot records written.
218    pub(crate) fn apply_block(
219        &self,
220        header: &bal_source::Header,
221        bal: &bal_codec::BlockAccessList,
222        watches: &[(Address, u64)],
223        verified: bool,
224        prune_hashes_below: Option<u64>,
225    ) -> Result<(FreshSlots, usize)> {
226        let n = header.number;
227        let provenance = if verified {
228            Provenance::Bal
229        } else {
230            Provenance::Unverified
231        };
232        let mut fresh = Vec::new();
233        let mut written = 0usize;
234        let txn = self.db.begin_write()?;
235        {
236            let mut slots = txn.open_table(SLOTS)?;
237            let mut idx = txn.open_table(BLOCKIDX)?;
238            let mut boot = txn.open_table(BOOT)?;
239            let mut pending = txn.open_table(PENDING)?;
240            let mut created = txn.open_table(CREATED)?;
241            let watch_now = txn.open_table(WATCH)?;
242            for (addr, start) in watches {
243                if n < *start {
244                    continue;
245                }
246                // Snapshot vs. now: an `unwatch()` since the snapshot must not
247                // leave orphan records behind.
248                if watch_now.get(addr.as_slice())?.is_none() {
249                    continue;
250                }
251                let Some(acc) = bal.account(addr) else {
252                    continue;
253                };
254                // Creation seen in a verified BAL: from here on no slot of
255                // this address needs a proof. Only a verified BAL may say so.
256                let mut is_created = created.get(addr.as_slice())?.is_some();
257                if !is_created && verified && creation_in(acc) {
258                    created.insert(addr.as_slice(), n)?;
259                    settle_created(&mut boot, &mut pending, *addr)?;
260                    is_created = true;
261                }
262                let mut fresh_here = Vec::new();
263                let mut changed = Vec::with_capacity(acc.storage_changes.len());
264                for sc in &acc.storage_changes {
265                    let slot = sc.slot_b256();
266                    changed.push(slot);
267                    let prefix = slot_prefix(*addr, slot);
268                    let seen_before = boot.get(prefix.as_slice())?.is_some();
269                    if !seen_before && is_created {
270                        boot.insert(prefix.as_slice(), encode_boot(BootState::Done).as_slice())?;
271                    } else if !seen_before {
272                        boot.insert(
273                            prefix.as_slice(),
274                            encode_boot(BootState::Pending { first_seen: n }).as_slice(),
275                        )?;
276                        pending.insert(prefix.as_slice(), n)?;
277                        fresh_here.push(slot);
278                    }
279                    if self.config.full_detail {
280                        for ch in &sc.changes {
281                            slots.insert(
282                                slot_key(*addr, slot, n, ch.block_access_index).as_slice(),
283                                encode_value(provenance, ch.value_b256()).as_slice(),
284                            )?;
285                            written += 1;
286                        }
287                    } else {
288                        let ch = sc.final_change();
289                        slots.insert(
290                            slot_key(*addr, slot, n, ch.block_access_index).as_slice(),
291                            encode_value(provenance, ch.value_b256()).as_slice(),
292                        )?;
293                        written += 1;
294                    }
295                }
296                if !changed.is_empty() {
297                    idx.insert(
298                        blockidx_key(*addr, n).as_slice(),
299                        encode_slots(&changed).as_slice(),
300                    )?;
301                }
302                if !fresh_here.is_empty() {
303                    fresh.push((*addr, *start, fresh_here));
304                }
305            }
306            let mut hashes = txn.open_table(HASHES)?;
307            hashes.insert(n, header_bytes(header.hash, header.state_root).as_slice())?;
308            if let Some(below) = prune_hashes_below {
309                let old: Vec<u64> = hashes
310                    .range(..below)?
311                    .map(|r| r.map(|(k, _)| k.value()))
312                    .collect::<std::result::Result<_, _>>()?;
313                for b in old {
314                    hashes.remove(b)?;
315                }
316            }
317            let mut meta = txn.open_table(META)?;
318            meta.insert(META_HEAD, head_bytes(n, header.hash).as_slice())?;
319        }
320        txn.commit()?;
321        Ok((fresh, written))
322    }
323}