substreams_ethereum_core/
block_view.rs

1use prost_types::Timestamp;
2
3use crate::pb::eth::v2::{Call, Log};
4use crate::{pb::eth::v2 as pb, Event};
5
6impl pb::Block {
7    /// Iterates over successful transactions
8    pub fn transactions(&self) -> impl Iterator<Item = &pb::TransactionTrace> {
9        self.transaction_traces.iter().filter(|tx| tx.status == 1)
10    }
11
12    /// Iterates over transaction receipts of successful transactions.
13    pub fn receipts(&self) -> impl Iterator<Item = ReceiptView> {
14        self.transactions().map(|transaction| transaction.receipt())
15    }
16
17    /// Iterates over logs in receipts of succesful transactions.
18    pub fn logs(&self) -> impl Iterator<Item = LogView> {
19        self.receipts().map(|receipt| receipt.logs()).flatten()
20    }
21
22    /// Iterates over calls of successful transactions.
23    pub fn calls(&self) -> impl Iterator<Item = CallView> {
24        self.transactions().map(|trx| trx.calls()).flatten()
25    }
26
27    /// A convenience for handlers that process a single type of event. Returns an iterator over
28    /// pairs of `(event, log)`.
29    ///
30    /// If you need to process multiple event types in a single handler, try something like:
31    /// ```ignore
32    /// for log in block.logs() {
33    ///     if !addresses.contains(&log.address()) {
34    ///        continue;
35    ///     }
36    ///
37    ///     if let Some(event) = E1::match_and_decode(log) {
38    ///         // Process events of type E1
39    ///     } else if let Some(event) = E2::match_and_decode(log) {
40    ///         // Process events of type E2
41    ///     }
42    /// }
43    /// ```
44    pub fn events<'a, E: Event>(
45        &'a self,
46        addresses: &'a [&[u8]],
47    ) -> impl Iterator<Item = (E, LogView<'a>)> {
48        self.logs().filter_map(|log| {
49            if !addresses.contains(&log.address()) {
50                return None;
51            }
52
53            E::match_and_decode(log).map(|e| (e, log))
54        })
55    }
56
57    /// Timestamp returns a reference to the block's header timestamp.
58    pub fn timestamp(&self) -> &Timestamp {
59        self.header.as_ref().unwrap().timestamp.as_ref().unwrap()
60    }
61
62    /// Timestamp returns block's header timestamp in seconds.
63    pub fn timestamp_seconds(&self) -> u64 {
64        self.header
65            .as_ref()
66            .unwrap()
67            .timestamp
68            .as_ref()
69            .unwrap()
70            .seconds as u64
71    }
72}
73
74#[derive(Copy, Clone)]
75pub struct ReceiptView<'a> {
76    pub transaction: &'a pb::TransactionTrace,
77    pub receipt: &'a pb::TransactionReceipt,
78}
79
80#[derive(Copy, Clone)]
81pub struct LogView<'a> {
82    pub receipt: ReceiptView<'a>,
83    pub log: &'a pb::Log,
84}
85
86#[derive(Copy, Clone, Debug, PartialEq)]
87pub struct CallView<'a> {
88    pub transaction: &'a pb::TransactionTrace,
89    pub call: &'a pb::Call,
90}
91
92impl CallView<'_> {
93    pub fn parent(&self) -> Option<&Call> {
94        return self
95            .transaction
96            .calls
97            .iter()
98            .find(|call| call.index == self.call.parent_index);
99    }
100}
101
102impl AsRef<pb::Call> for CallView<'_> {
103    fn as_ref(&self) -> &pb::Call {
104        self.call
105    }
106}
107
108impl pb::TransactionTrace {
109    pub fn calls(&self) -> impl Iterator<Item = CallView> {
110        self.calls.iter().map(move |call| CallView {
111            transaction: self,
112            call,
113        })
114    }
115
116    pub fn receipt(&self) -> ReceiptView {
117        ReceiptView {
118            transaction: self,
119            receipt: &self.receipt.as_ref().unwrap(),
120        }
121    }
122
123    /// Iterates over all logs in the transaction, excluding those from calls that were not
124    /// recorded to the chain's state.
125    ///
126    /// The logs are sorted by their ordinal and returned as pairs of `(log, call)` where `call`
127    /// is the call that produced the log.
128    pub fn logs_with_calls(&self) -> impl Iterator<Item = (&Log, CallView)> {
129        let mut res: Vec<(&Log, CallView)> = Vec::with_capacity(
130            self.calls
131                .iter()
132                .filter(|call| !call.state_reverted)
133                .map(|call| call.logs.len())
134                .sum(),
135        );
136
137        for call in self.calls.iter() {
138            if call.state_reverted {
139                continue;
140            }
141
142            for log in call.logs.iter() {
143                res.push((
144                    &log,
145                    CallView {
146                        transaction: self,
147                        call,
148                    },
149                ));
150            }
151        }
152
153        res.sort_by(|x, y| x.0.ordinal.cmp(&y.0.ordinal));
154        res.into_iter()
155    }
156
157    // TODO: Call view, filtering out failed calls
158    // pub fn calls: Vec<CallView> { }
159}
160
161impl<'a> ReceiptView<'a> {
162    pub fn state_root(self) -> &'a [u8] {
163        &self.receipt.state_root
164    }
165
166    pub fn cumulative_gas_used(self) -> u64 {
167        self.receipt.cumulative_gas_used
168    }
169
170    pub fn logs_bloom(self) -> &'a [u8] {
171        &self.receipt.logs_bloom
172    }
173
174    pub fn logs(self) -> impl Iterator<Item = LogView<'a>> {
175        self.receipt
176            .logs
177            .iter()
178            .map(move |log| LogView { receipt: self, log })
179    }
180}
181
182impl<'a> LogView<'a> {
183    pub fn address(self) -> &'a [u8] {
184        &self.log.address
185    }
186
187    pub fn topics(self) -> &'a Vec<Vec<u8>> {
188        &self.log.topics
189    }
190
191    pub fn data(self) -> &'a [u8] {
192        &self.log.data
193    }
194
195    pub fn index(self) -> u32 {
196        self.log.index
197    }
198
199    pub fn block_index(self) -> u32 {
200        self.log.block_index
201    }
202
203    pub fn ordinal(self) -> u64 {
204        self.log.ordinal
205    }
206}
207
208impl AsRef<pb::Log> for LogView<'_> {
209    fn as_ref(&self) -> &pb::Log {
210        self.log
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use std::vec;
217
218    use crate::{
219        block_view::CallView,
220        pb::eth::v2::{Call, Log, TransactionTrace},
221    };
222
223    #[test]
224    fn logs_with_calls() {
225        let call = |to: &str, state_reverted, logs| Call {
226            address: to.to_string().into_bytes(),
227            state_reverted,
228            logs,
229            ..Default::default()
230        };
231
232        let log = |ordinal| Log {
233            ordinal,
234            ..Default::default()
235        };
236
237        let trace = TransactionTrace {
238            calls: vec![
239                call("1", true, vec![log(0)]),
240                call("2", false, vec![log(8), log(2)]),
241                call("3", false, vec![log(4)]),
242                call("4", true, vec![log(1), log(3)]),
243            ],
244            ..Default::default()
245        };
246
247        let call_at = |call_index: usize| CallView {
248            call: trace.calls.get(call_index).unwrap(),
249            transaction: &trace,
250        };
251
252        let log_at = |call_index: usize, log_index: usize| {
253            call_at(call_index).call.logs.get(log_index).unwrap()
254        };
255
256        assert_eq!(
257            Vec::from_iter(trace.logs_with_calls()),
258            vec![
259                (log_at(1, 1), call_at(1)),
260                (log_at(2, 0), call_at(2)),
261                (log_at(1, 0), call_at(1)),
262            ]
263        );
264    }
265}