substreams_antelope_core/
block.rs

1use crate::block::pb::TransactionStatus::TransactionstatusExecuted;
2use crate::pb;
3
4impl pb::Block {
5    /// returns all executed transaction traces from the block without consuming it.
6    /// ```ignore
7    /// let transactions = block.transaction_traces()
8    ///     .map(|trx| {
9    ///         // your transaction logic
10    ///     })
11    ///     .collect();
12    /// ```
13    pub fn transaction_traces(&self) -> impl Iterator<Item = &pb::TransactionTrace> {
14        let traces = if self.filtering_applied {
15            self.filtered_transaction_traces.iter()
16        } else {
17            self.unfiltered_transaction_traces.iter()
18        };
19
20        traces.filter(|trx| trx.receipt.is_some() && trx.receipt.as_ref().unwrap().status == TransactionstatusExecuted as i32)
21    }
22
23    /// returns all executed transaction traces from the block and consumes it.
24    /// ```ignore
25    /// let transactions = block.into_transaction_traces()
26    ///     .map(|trx| {
27    ///         // your transaction logic
28    ///     })
29    ///     .collect();
30    /// ```
31    pub fn into_transaction_traces(self) -> impl Iterator<Item = pb::TransactionTrace> {
32        let traces = if self.filtering_applied {
33            self.filtered_transaction_traces.into_iter()
34        } else {
35            self.unfiltered_transaction_traces.into_iter()
36        };
37
38        traces.filter(|trx| trx.receipt.is_some() && trx.receipt.as_ref().unwrap().status == TransactionstatusExecuted as i32)
39    }
40
41    /// returns all executed action traces along with related transaction traces from the block without consuming it
42    /// ```ignore
43    /// let actions = block.action_traces()
44    ///     .map(|(action, trx)| {
45    ///         // your action logic
46    ///     })
47    ///     .collect();
48    /// ```
49    pub fn action_traces(&self) -> impl Iterator<Item = (&pb::ActionTrace, &pb::TransactionTrace)> {
50        self.transaction_traces()
51            .flat_map(|trx| trx.action_traces.iter().map(move |trace| (trace, trx)))
52    }
53
54    /// returns all executed action traces from the block and consumes it. Transaction traces are not returned.
55    /// ```ignore
56    /// let actions = block.into_action_traces()
57    ///     .map(|action| {
58    ///         // your action logic
59    ///     })
60    ///     .collect();
61    /// ```
62    pub fn into_action_traces(self) -> impl Iterator<Item = pb::ActionTrace> {
63        self.into_transaction_traces().flat_map(|trx| trx.action_traces)
64    }
65
66    /// returns all executed actions and notifications of a specified type from the block which match the given contract accounts, or all if account is empty
67    /// ```ignore
68    /// let actions = block.all_actions::<abi::contract::actions::Statelog>(&["mycontract"])
69    ///     .map(|(action, trace, trx)| StateChange {
70    ///         // set action fields
71    ///     })
72    ///     .collect();
73    /// ```
74    pub fn all_actions<'a, A: crate::action::Action>(
75        &'a self,
76        accounts: &'a [&str],
77    ) -> impl Iterator<Item = (A, &pb::ActionTrace, &pb::TransactionTrace)> + 'a {
78        self.action_traces().filter_map(|(trace, trx)| {
79            let contract = trace.action.as_ref().unwrap().account.as_str();
80            if !accounts.is_empty() && !accounts.contains(&contract) {
81                return None;
82            }
83
84            A::match_and_decode(trace).map(|action| (action, trace, trx))
85        })
86    }
87
88    /// returns all actions of a specified type from the block which match the given contract accounts
89    /// NOT including action notifications
90    /// ```ignore
91    /// let actions = block.actions::<abi::contract::actions::Statelog>(&["mycontract"])
92    ///     .map(|(action, trx)| StateChange {
93    ///         // set action fields
94    ///     })
95    ///     .collect();
96    /// ```
97    pub fn actions<'a, A: crate::action::Action>(
98        &'a self,
99        accounts: &'a [&str],
100    ) -> impl Iterator<Item = (A, &pb::ActionTrace, &pb::TransactionTrace)> + 'a {
101        self.all_actions(accounts)
102            .filter(|(_, trace, _)| trace.receiver.as_str() == trace.action.as_ref().unwrap().account.as_str())
103    }
104
105    /// returns all action notifications of a specified type from the block which match the given contract accounts
106    /// ONLY including action notifications
107    /// ```ignore
108    /// let notifications = block.notifications::<abi::contract::actions::Statelog>(&["mycontract"])
109    ///     .map(|(action, trx)| StateChange {
110    ///         // set action fields
111    ///     })
112    ///     .collect();
113    /// ```
114    pub fn notifications<'a, A: crate::action::Action>(
115        &'a self,
116        accounts: &'a [&str],
117    ) -> impl Iterator<Item = (A, &pb::ActionTrace, &pb::TransactionTrace)> + 'a {
118        self.all_actions(accounts)
119            .filter(|(_, trace, _)| trace.receiver.as_str() != trace.action.as_ref().unwrap().account.as_str())
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use crate::block::pb::TransactionStatus::{TransactionstatusExecuted, TransactionstatusSoftfail};
127
128    // Helper function to create a test pb::Block instance
129    fn create_test_block(
130        filtering_applied: bool,
131        unfiltered_transaction_traces: Vec<pb::TransactionTrace>,
132        filtered_transaction_traces: Vec<pb::TransactionTrace>,
133        unfiltered_transaction_count: u32,
134        filtered_transaction_count: u32,
135        unfiltered_executed_input_action_count: u32,
136        filtered_executed_input_action_count: u32,
137        unfiltered_executed_total_action_count: u32,
138        filtered_executed_total_action_count: u32,
139    ) -> pb::Block {
140        pb::Block {
141            filtering_applied,
142            unfiltered_transaction_traces,
143            filtered_transaction_traces,
144            unfiltered_transaction_count,
145            filtered_transaction_count,
146            unfiltered_executed_input_action_count,
147            filtered_executed_input_action_count,
148            unfiltered_executed_total_action_count,
149            filtered_executed_total_action_count,
150            ..Default::default()
151        }
152    }
153
154    #[test]
155    fn test_all_transaction_traces() {
156        let unfiltered_traces = vec![
157            pb::TransactionTrace {
158                id: String::from("trx1"),
159                receipt: Some(pb::TransactionReceiptHeader {
160                    status: TransactionstatusExecuted as i32,
161                    ..Default::default()
162                }),
163                ..Default::default()
164            },
165            pb::TransactionTrace {
166                id: String::from("trx2"),
167                receipt: Some(pb::TransactionReceiptHeader {
168                    status: TransactionstatusExecuted as i32,
169                    ..Default::default()
170                }),
171                ..Default::default()
172            },
173        ];
174        let block = create_test_block(false, unfiltered_traces.clone(), vec![], 2, 0, 5, 0, 7, 0);
175
176        let all_traces: Vec<_> = block.into_transaction_traces().collect();
177        assert_eq!(all_traces, unfiltered_traces);
178    }
179
180    #[test]
181    fn test_all_action_traces() {
182        let unfiltered_traces = vec![
183            pb::TransactionTrace {
184                id: String::from("trx1"),
185                receipt: Some(pb::TransactionReceiptHeader {
186                    status: TransactionstatusExecuted as i32,
187                    ..Default::default()
188                }),
189                action_traces: vec![pb::ActionTrace { ..Default::default() }],
190                ..Default::default()
191            },
192            pb::TransactionTrace {
193                id: String::from("trx2"),
194                receipt: Some(pb::TransactionReceiptHeader {
195                    status: TransactionstatusExecuted as i32,
196                    ..Default::default()
197                }),
198                action_traces: vec![pb::ActionTrace { ..Default::default() }, pb::ActionTrace { ..Default::default() }],
199                ..Default::default()
200            },
201        ];
202        let block = create_test_block(false, unfiltered_traces.clone(), vec![], 2, 0, 5, 0, 7, 0);
203
204        let all_traces: Vec<_> = block.action_traces().collect();
205        assert_eq!(all_traces.len(), 3);
206    }
207
208    #[test]
209    fn test_executed_transaction_traces() {
210        let executed_traces = vec![
211            pb::TransactionTrace {
212                id: String::from("trx1"),
213                receipt: Some(pb::TransactionReceiptHeader {
214                    status: TransactionstatusExecuted as i32,
215                    ..Default::default()
216                }),
217                ..Default::default()
218            },
219            pb::TransactionTrace {
220                id: String::from("trx2"),
221                receipt: Some(pb::TransactionReceiptHeader {
222                    status: TransactionstatusExecuted as i32,
223                    ..Default::default()
224                }),
225                ..Default::default()
226            },
227        ];
228        let block = create_test_block(false, executed_traces.clone(), vec![], 2, 0, 5, 0, 7, 0);
229
230        let produced_traces: Vec<_> = block.transaction_traces().cloned().collect();
231        assert_eq!(executed_traces, produced_traces);
232    }
233
234    #[test]
235    fn test_executed_transaction_traces_no_receipt() {
236        let executed_traces = vec![
237            pb::TransactionTrace {
238                id: String::from("trx1"),
239                receipt: Some(pb::TransactionReceiptHeader {
240                    status: TransactionstatusExecuted as i32,
241                    ..Default::default()
242                }),
243                ..Default::default()
244            },
245            pb::TransactionTrace {
246                id: String::from("trx2"),
247                receipt: Some(pb::TransactionReceiptHeader {
248                    status: TransactionstatusSoftfail as i32,
249                    ..Default::default()
250                }),
251                ..Default::default()
252            },
253            pb::TransactionTrace {
254                id: String::from("trx3"),
255                receipt: None,
256                ..Default::default()
257            },
258        ];
259        let block = create_test_block(false, executed_traces.clone(), vec![], 2, 0, 5, 0, 7, 0);
260
261        let produced_traces: Vec<_> = block.transaction_traces().cloned().collect();
262        assert_eq!(executed_traces[0], produced_traces[0]);
263        assert_eq!(produced_traces.len(), 1);
264    }
265}