substreams_ethereum_core/
block_view.rs1use prost_types::Timestamp;
2
3use crate::pb::eth::v2::{Call, Log};
4use crate::{pb::eth::v2 as pb, Event};
5
6impl pb::Block {
7 pub fn transactions(&self) -> impl Iterator<Item = &pb::TransactionTrace> {
9 self.transaction_traces.iter().filter(|tx| tx.status == 1)
10 }
11
12 pub fn receipts(&self) -> impl Iterator<Item = ReceiptView> {
14 self.transactions().map(|transaction| transaction.receipt())
15 }
16
17 pub fn logs(&self) -> impl Iterator<Item = LogView> {
19 self.receipts().map(|receipt| receipt.logs()).flatten()
20 }
21
22 pub fn calls(&self) -> impl Iterator<Item = CallView> {
24 self.transactions().map(|trx| trx.calls()).flatten()
25 }
26
27 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 pub fn timestamp(&self) -> &Timestamp {
59 self.header.as_ref().unwrap().timestamp.as_ref().unwrap()
60 }
61
62 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 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 }
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}