primitives/
filter.rs

1// Copyright 2015-2018 Parity Technologies (UK) Ltd.
2// This file is part of Parity.
3
4// Parity is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity.  If not, see <http://www.gnu.org/licenses/>.
16
17// Copyright 2019 Conflux Foundation. All rights reserved.
18// Conflux is free software and distributed under GNU General Public License.
19// See http://www.gnu.org/licenses/
20
21//! Blockchain filter
22
23use crate::{epoch::EpochNumber, log_entry::LogEntry};
24use cfx_types::{Address, Bloom, BloomInput, Space, H256};
25use cfxcore_errors::ProviderBlockError;
26use std::{
27    error, fmt,
28    ops::{Deref, DerefMut},
29};
30
31#[derive(Debug, PartialEq, Clone)]
32/// Errors concerning log filtering.
33pub enum FilterError {
34    /// Filter has wrong epoch numbers set.
35    InvalidEpochNumber {
36        from_epoch: u64,
37        to_epoch: u64,
38    },
39
40    /// Filter has wrong block numbers set.
41    InvalidBlockNumber {
42        from_block: u64,
43        to_block: u64,
44    },
45
46    OutOfBoundEpochNumber {
47        to_epoch: u64,
48        max_epoch: u64,
49    },
50
51    EpochNumberGapTooLarge {
52        from_epoch: u64,
53        to_epoch: u64,
54        max_gap: u64,
55    },
56
57    BlockNumberGapTooLarge {
58        from_block: u64,
59        to_block: u64,
60        max_gap: u64,
61    },
62
63    /// Roots for verifying the requested epochs are unavailable.
64    UnableToVerify {
65        epoch: u64,
66        latest_verifiable: u64,
67    },
68
69    /// The block requested does not exist
70    UnknownBlock {
71        hash: H256,
72    },
73
74    /// Epoch cannot be served as it was already pruned from db on a full node
75    EpochAlreadyPruned {
76        epoch: u64,
77        min: u64,
78    },
79
80    /// Block cannot be served as it was already pruned from db on a full node
81    // Use this when the corresponding epoch is not known.
82    BlockAlreadyPruned {
83        block_hash: H256,
84    },
85
86    /// Block has not been executed yet
87    BlockNotExecutedYet {
88        block_hash: H256,
89    },
90
91    /// There was a pivot chain reorganization during log filtering
92    PivotChainReorg {
93        epoch: u64,
94        from: H256,
95        to: H256,
96    },
97
98    /// Filter error with custom error message (e.g. timeout)
99    Custom(String),
100}
101
102impl From<String> for FilterError {
103    fn from(s: String) -> Self { FilterError::Custom(s) }
104}
105
106impl From<&str> for FilterError {
107    fn from(s: &str) -> Self { FilterError::Custom(s.to_string()) }
108}
109
110impl From<ProviderBlockError> for FilterError {
111    fn from(err: ProviderBlockError) -> Self {
112        FilterError::from(err.to_string())
113    }
114}
115
116impl fmt::Display for FilterError {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        use self::FilterError::*;
119        let msg = match *self {
120            InvalidEpochNumber {
121                from_epoch,
122                to_epoch,
123            } => format! {
124                "Filter has wrong epoch numbers set (from: {}, to: {})",
125                from_epoch, to_epoch
126            },
127            InvalidBlockNumber {
128                from_block,
129                to_block,
130            } => format! {
131                "Filter has wrong block numbers set (from: {}, to: {})",
132                from_block, to_block
133            },
134            OutOfBoundEpochNumber {
135                to_epoch,
136                max_epoch,
137            } => format! {
138                "Filter to_epoch is larger than the current best_epoch (to: {}, max: {})",
139                to_epoch, max_epoch,
140            },
141            EpochNumberGapTooLarge {
142                from_epoch,
143                to_epoch,
144                max_gap,
145            } => {
146                format! {
147                    "The gap between from_epoch and to_epoch is larger than max_gap \
148                    (from: {}, to: {}, max_gap: {})",
149                    from_epoch, to_epoch, max_gap
150                }
151            }
152            BlockNumberGapTooLarge {
153                from_block,
154                to_block,
155                max_gap,
156            } => {
157                format! {
158                    "The gap between from_block and to_block is larger than max_gap \
159                    (from: {}, to: {}, max_gap: {})",
160                    from_block, to_block, max_gap
161                }
162            }
163            UnableToVerify {
164                epoch,
165                latest_verifiable,
166            } => format! {
167                "Unable to verify epoch {} (latest verifiable epoch is {})",
168                epoch, latest_verifiable
169            },
170            UnknownBlock { hash } => format! {
171                "Unable to identify block {:?}", hash
172            },
173            EpochAlreadyPruned { epoch, min } => format! {
174                "Epoch is smaller than the earliest epoch stored (epoch: {}, min: {})",
175                epoch, min,
176            },
177            BlockAlreadyPruned { block_hash } => format! {
178                "Block {:?} has been pruned from db", block_hash,
179            },
180            BlockNotExecutedYet { block_hash } => format! {
181                "Block {:?} is not executed yet", block_hash,
182            },
183            PivotChainReorg { epoch, from, to } => format! {
184                "Pivot chain at epoch {} has been reorganized during log filtering: {:?} -> {:?}. Operation terminated to avoid inconsistent results.",
185                epoch, from, to,
186            },
187            Custom(ref s) => s.clone(),
188        };
189
190        f.write_fmt(format_args!("Filter error: {}", msg))
191    }
192}
193
194impl error::Error for FilterError {
195    fn description(&self) -> &str { "Filter error" }
196}
197
198#[derive(Clone, Debug, PartialEq, Eq, Hash)]
199pub enum LogFilter {
200    EpochLogFilter {
201        from_epoch: EpochNumber,
202        to_epoch: EpochNumber,
203        params: LogFilterParams,
204    },
205    BlockHashLogFilter {
206        block_hashes: Vec<H256>,
207        params: LogFilterParams,
208    },
209    BlockNumberLogFilter {
210        from_block: u64,
211        to_block: u64,
212        params: LogFilterParams,
213    },
214}
215
216/// Log event Filter.
217#[derive(Clone, Debug, PartialEq, Eq, Hash)]
218pub struct LogFilterParams {
219    /// Search addresses.
220    ///
221    /// If None, match all.
222    /// If specified, log must be produced by one of these addresses.
223    pub address: Option<Vec<Address>>,
224
225    /// Search topics.
226    ///
227    /// If None, match all.
228    /// If specified, log must contain one of these topics.
229    pub topics: Vec<Option<Vec<H256>>>,
230
231    /// Indicate if the log filter can be trusted, so we do not need to check
232    /// other fields.
233    ///
234    /// It is `false` if the Filter is constructed from RPCs,
235    /// and `true` if it is generated within the process with trusted logics.
236    pub trusted: bool,
237
238    /// Space: Conflux or Ethereum.
239    ///
240    /// Log must be produced in this space.
241    pub space: Space,
242}
243
244impl Default for LogFilterParams {
245    fn default() -> Self {
246        LogFilterParams {
247            address: None,
248            topics: vec![None, None, None, None],
249            trusted: false,
250            space: Space::Native,
251        }
252    }
253}
254
255impl Default for LogFilter {
256    fn default() -> Self {
257        LogFilter::EpochLogFilter {
258            from_epoch: EpochNumber::LatestCheckpoint,
259            to_epoch: EpochNumber::LatestState,
260            params: Default::default(),
261        }
262    }
263}
264
265impl Deref for LogFilter {
266    type Target = LogFilterParams;
267
268    fn deref(&self) -> &Self::Target {
269        match &self {
270            &LogFilter::EpochLogFilter { params, .. } => params,
271            &LogFilter::BlockHashLogFilter { params, .. } => params,
272            &LogFilter::BlockNumberLogFilter { params, .. } => params,
273        }
274    }
275}
276
277impl DerefMut for LogFilter {
278    fn deref_mut(&mut self) -> &mut Self::Target {
279        match self {
280            LogFilter::EpochLogFilter { params, .. } => params,
281            LogFilter::BlockHashLogFilter { params, .. } => params,
282            LogFilter::BlockNumberLogFilter { params, .. } => params,
283        }
284    }
285}
286
287impl LogFilterParams {
288    /// Returns combinations of each address and topic.
289    pub fn bloom_possibilities(&self) -> Vec<Bloom> {
290        let blooms = match self.address {
291            Some(ref addresses) if !addresses.is_empty() => addresses
292                .iter()
293                .map(|ref address| {
294                    Bloom::from(BloomInput::Raw(address.as_bytes()))
295                })
296                .collect(),
297            _ => vec![Bloom::default()],
298        };
299
300        self.topics.iter().fold(blooms, |bs, topic| match *topic {
301            None => bs,
302            Some(ref topics) => bs
303                .into_iter()
304                .flat_map(|bloom| {
305                    topics
306                        .iter()
307                        .map(|topic| {
308                            let mut b = bloom.clone();
309                            b.accrue(BloomInput::Raw(topic.as_bytes()));
310                            b
311                        })
312                        .collect::<Vec<Bloom>>()
313                })
314                .collect(),
315        })
316    }
317
318    /// Returns true if given log entry matches filter.
319    pub fn matches(&self, log: &LogEntry) -> bool {
320        if log.space != self.space {
321            return false;
322        }
323
324        let matches = match self.address {
325            Some(ref addresses) if !addresses.is_empty() => {
326                addresses.iter().any(|address| &log.address == address)
327            }
328            _ => true,
329        };
330
331        matches
332            && self
333                .topics
334                .iter()
335                .enumerate()
336                .all(|(i, topic)| match *topic {
337                    Some(ref topics) if !topics.is_empty() => topics
338                        .iter()
339                        .any(|topic| log.topics.get(i) == Some(topic)),
340                    _ => true,
341                })
342    }
343}