Skip to main content

bal_archive/
reads.rs

1//! Reads: one ordered seek per value, and a typed reason for every miss.
2
3use super::*;
4
5impl Archive {
6    fn check_range(&self, addr: Address, block: u64) -> std::result::Result<u64, NotAvailable> {
7        let start = self.start_of(addr)?.ok_or(NotAvailable::NotWatched(addr))?;
8        if block < start {
9            return Err(NotAvailable::BeforeStart {
10                requested: block,
11                start,
12            });
13        }
14        let (head, _) = self.head()?.ok_or(NotAvailable::NotSynced)?;
15        if block > head {
16            return Err(NotAvailable::AfterHead {
17                requested: block,
18                head,
19            });
20        }
21        Ok(start)
22    }
23
24    /// Value of `slot` at the end of `block`. One ordered seek.
25    pub fn storage_at(
26        &self,
27        addr: Address,
28        slot: B256,
29        block: u64,
30    ) -> std::result::Result<StorageValue, NotAvailable> {
31        self.check_range(addr, block)?;
32        let rtx = self.db.begin_read().map_err(ArchiveError::from)?;
33        let t = rtx.open_table(SLOTS).map_err(ArchiveError::from)?;
34        let lo = slot_prefix(addr, slot);
35        let hi = slot_key(addr, slot, block, u32::MAX);
36        let mut it = t
37            .range::<&[u8]>(lo.as_slice()..=hi.as_slice())
38            .map_err(ArchiveError::from)?;
39        if let Some(item) = it.next_back() {
40            let (k, v) = item.map_err(ArchiveError::from)?;
41            let (_, _, set_at, index) =
42                parse_slot_key(k.value()).ok_or(NotAvailable::Internal("bad slot key".into()))?;
43            let (provenance, value) =
44                decode_value(v.value()).ok_or(NotAvailable::Internal("bad slot value".into()))?;
45            return Ok(StorageValue {
46                value,
47                provenance,
48                set_at,
49                index,
50            });
51        }
52        drop(it);
53        // No record at or before `block`. If the contract's creation was
54        // seen, the slot was never written between creation and `block` (BAL
55        // completeness), and before creation the account had no storage: the
56        // value is zero, and that is a fact from a verified BAL, not a guess.
57        let created = rtx.open_table(CREATED).map_err(ArchiveError::from)?;
58        if let Some(c) = created
59            .get(addr.as_slice())
60            .map_err(ArchiveError::from)?
61            .map(|v| v.value())
62        {
63            return Ok(StorageValue {
64                value: B256::ZERO,
65                provenance: Provenance::Bal,
66                set_at: c,
67                index: u32::MAX,
68            });
69        }
70        // Otherwise the slot's earliest change is later and nothing is known
71        // before it, or it never changed at all.
72        let boot = rtx.open_table(BOOT).map_err(ArchiveError::from)?;
73        match boot.get(lo.as_slice()).map_err(ArchiveError::from)? {
74            Some(v) => match decode_boot(v.value()) {
75                Some(BootState::Pending { first_seen }) | Some(BootState::Lost { first_seen }) => {
76                    Err(NotAvailable::UnknownBefore { first_seen })
77                }
78                Some(BootState::Done) => Err(NotAvailable::Internal(
79                    "bootstrap marked done but no record".into(),
80                )),
81                None => Err(NotAvailable::Internal("bad bootstrap record".into())),
82            },
83            None => Err(NotAvailable::NeverRecorded),
84        }
85    }
86
87    /// All recorded changes of `slot` in `range` (half-open), ascending.
88    /// Bootstrap records live at `start - 1` and are therefore never inside a
89    /// valid range; what comes back is BAL data only.
90    pub fn history(
91        &self,
92        addr: Address,
93        slot: B256,
94        range: Range<u64>,
95    ) -> std::result::Result<Vec<HistoryEntry>, NotAvailable> {
96        if range.start >= range.end {
97            return Err(NotAvailable::InvalidRange {
98                start: range.start,
99                end: range.end,
100            });
101        }
102        self.check_range(addr, range.start)?;
103        self.check_range(addr, range.end - 1)?;
104        let rtx = self.db.begin_read().map_err(ArchiveError::from)?;
105        let t = rtx.open_table(SLOTS).map_err(ArchiveError::from)?;
106        let lo = slot_key(addr, slot, range.start, 0);
107        let hi = slot_key(addr, slot, range.end, 0);
108        let mut out = Vec::new();
109        for item in t
110            .range::<&[u8]>(lo.as_slice()..hi.as_slice())
111            .map_err(ArchiveError::from)?
112        {
113            let (k, v) = item.map_err(ArchiveError::from)?;
114            let (_, _, block, index) =
115                parse_slot_key(k.value()).ok_or(NotAvailable::Internal("bad slot key".into()))?;
116            let (provenance, value) =
117                decode_value(v.value()).ok_or(NotAvailable::Internal("bad slot value".into()))?;
118            out.push(HistoryEntry {
119                block,
120                index,
121                value,
122                provenance,
123            });
124        }
125        Ok(out)
126    }
127
128    /// Slots of `addr` written in `block`, from the block index.
129    pub fn changed_slots(
130        &self,
131        addr: Address,
132        block: u64,
133    ) -> std::result::Result<Vec<B256>, NotAvailable> {
134        self.check_range(addr, block)?;
135        let rtx = self.db.begin_read().map_err(ArchiveError::from)?;
136        let t = rtx.open_table(BLOCKIDX).map_err(ArchiveError::from)?;
137        match t
138            .get(blockidx_key(addr, block).as_slice())
139            .map_err(ArchiveError::from)?
140        {
141            Some(v) => {
142                decode_slots(v.value()).ok_or(NotAvailable::Internal("bad blockidx value".into()))
143            }
144            None => Ok(Vec::new()),
145        }
146    }
147
148    /// Bootstrap state of a slot, if it has ever been seen or proven.
149    pub fn boot_state(&self, addr: Address, slot: B256) -> Result<Option<BootState>> {
150        let rtx = self.db.begin_read()?;
151        let t = rtx.open_table(BOOT)?;
152        Ok(t.get(slot_prefix(addr, slot).as_slice())?
153            .and_then(|v| decode_boot(v.value())))
154    }
155}