namada_events/
lib.rs

1//! Events emitted by the Namada ledger.
2
3#![doc(html_favicon_url = "https://dev.namada.net/master/favicon.png")]
4#![doc(html_logo_url = "https://dev.namada.net/master/rustdoc-logo.png")]
5#![deny(rustdoc::broken_intra_doc_links)]
6#![deny(rustdoc::private_intra_doc_links)]
7#![warn(
8    missing_docs,
9    rust_2018_idioms,
10    clippy::cast_sign_loss,
11    clippy::cast_possible_truncation,
12    clippy::cast_possible_wrap,
13    clippy::cast_lossless,
14    clippy::arithmetic_side_effects,
15    clippy::dbg_macro,
16    clippy::print_stdout,
17    clippy::print_stderr
18)]
19
20pub mod extend;
21#[cfg(any(test, feature = "testing"))]
22pub mod testing;
23#[cfg(any(test, feature = "debug"))]
24pub mod tracer;
25
26use std::borrow::Cow;
27use std::collections::BTreeMap;
28use std::fmt::{self, Display};
29use std::ops::Deref;
30use std::str::FromStr;
31
32use namada_core::borsh::{BorshDeserialize, BorshSerialize};
33use namada_macros::BorshDeserializer;
34#[cfg(feature = "migrations")]
35use namada_migrations::*;
36use serde::{Deserialize, Serialize};
37use thiserror::Error;
38
39#[doc(hidden)]
40#[macro_export]
41macro_rules! __event_type_impl {
42    ($domain:ty) => {
43        <$domain as $crate::EventToEmit>::DOMAIN
44    };
45    ($domain:ty, $($subdomain:expr),*) => {
46        ::konst::string::str_join!(
47            "/",
48            &[
49                $crate::__event_type_impl!($domain),
50                $($subdomain),*
51            ],
52        )
53    };
54}
55
56/// Instantiate a new [`EventType`] in const contexts. Mostly
57/// useful to define new event types in the protocol.
58///
59/// # Example
60///
61/// ```ignore
62/// const RELAYED: EventType = event_type!(EthBridgeEvent, "bridge-pool", "relayed");
63/// ```
64#[macro_export]
65macro_rules! event_type {
66    ($($tt:tt)*) => {
67        $crate::EventType::new($crate::__event_type_impl!($($tt)*))
68    };
69}
70
71/// An event to be emitted in Namada.
72pub trait EventToEmit: Into<Event> {
73    /// The domain of the event to emit.
74    ///
75    /// This may be used to group events of a certain kind.
76    const DOMAIN: &'static str;
77}
78
79impl EventToEmit for Event {
80    const DOMAIN: &'static str = "unknown";
81}
82
83/// Used in sub-systems that may emit events.
84pub trait EmitEvents {
85    /// Emit a single [event](Event).
86    fn emit<E>(&mut self, event: E)
87    where
88        E: EventToEmit;
89
90    /// Emit a batch of [events](Event).
91    fn emit_many<B, E>(&mut self, event_batch: B)
92    where
93        B: IntoIterator<Item = E>,
94        E: EventToEmit;
95}
96
97impl EmitEvents for Vec<Event> {
98    #[inline]
99    fn emit<E>(&mut self, event: E)
100    where
101        E: Into<Event>,
102    {
103        self.push(event.into());
104    }
105
106    /// Emit a batch of [events](Event).
107    fn emit_many<B, E>(&mut self, event_batch: B)
108    where
109        B: IntoIterator<Item = E>,
110        E: Into<Event>,
111    {
112        self.extend(event_batch.into_iter().map(Into::into));
113    }
114}
115
116/// Indicates if an event is emitted do to
117/// an individual Tx or the nature of a finalized block
118#[derive(
119    Clone,
120    Debug,
121    Eq,
122    PartialEq,
123    Ord,
124    PartialOrd,
125    Hash,
126    BorshSerialize,
127    BorshDeserialize,
128    BorshDeserializer,
129    Serialize,
130    Deserialize,
131)]
132pub enum EventLevel {
133    /// Indicates an event is to do with a finalized block.
134    Block,
135    /// Indicates an event is to do with an individual transaction.
136    Tx,
137}
138
139impl Display for EventLevel {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(
142            f,
143            "{}",
144            match self {
145                EventLevel::Block => "block",
146                EventLevel::Tx => "tx",
147            }
148        )
149    }
150}
151
152/// ABCI event type.
153///
154/// It is comprised of an event domain and sub-domain, plus any other
155/// specifiers.
156#[derive(
157    Clone,
158    Debug,
159    Eq,
160    PartialEq,
161    Ord,
162    PartialOrd,
163    Hash,
164    BorshSerialize,
165    BorshDeserialize,
166    BorshDeserializer,
167    Serialize,
168    Deserialize,
169)]
170#[repr(transparent)]
171pub struct EventType {
172    inner: Cow<'static, str>,
173}
174
175impl Deref for EventType {
176    type Target = str;
177
178    #[inline(always)]
179    fn deref(&self) -> &str {
180        &self.inner
181    }
182}
183
184impl EventType {
185    /// Create a new event type.
186    pub const fn new(event_type: &'static str) -> Self {
187        Self {
188            inner: Cow::Borrowed(event_type),
189        }
190    }
191
192    /// Retrieve the domain of some event.
193    #[inline]
194    pub fn domain(&self) -> &str {
195        self.inner
196            .split_once('/')
197            .map(|(domain, _sub_domain)| domain)
198            .unwrap_or("unknown")
199    }
200
201    /// Retrieve the sub-domain of some event.
202    #[inline]
203    pub fn sub_domain(&self) -> &str {
204        self.inner
205            .split_once('/')
206            .map(|(_domain, sub_domain)| sub_domain)
207            .unwrap_or("")
208    }
209}
210
211impl Display for EventType {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        write!(f, "{}", self.inner)
214    }
215}
216
217impl FromStr for EventType {
218    type Err = EventError;
219
220    fn from_str(s: &str) -> Result<Self, Self::Err> {
221        s.split_once('/').ok_or(EventError::MissingDomain)?;
222        Ok(Self {
223            inner: Cow::Owned(s.into()),
224        })
225    }
226}
227
228/// Build an [`EventType`] segment by segment.
229pub struct EventTypeBuilder {
230    inner: String,
231}
232
233impl EventTypeBuilder {
234    /// Create a new [`EventTypeBuilder`] with the given type.
235    #[inline]
236    pub fn new_with_type(ty: impl Into<String>) -> Self {
237        Self { inner: ty.into() }
238    }
239
240    /// Create a new [`EventTypeBuilder`] with the domain of the
241    /// given event type.
242    #[inline]
243    pub fn new_of<E: EventToEmit>() -> Self {
244        Self::new_with_type(E::DOMAIN)
245    }
246
247    /// Append a new segment to the final [`EventType`] and return
248    /// a mutable reference to the builder.
249    #[inline]
250    pub fn append_segment(&mut self, segment: impl AsRef<str>) -> &mut Self {
251        let segment = segment.as_ref();
252
253        if !segment.is_empty() {
254            self.inner.push('/');
255            self.inner.push_str(segment.as_ref());
256        }
257
258        self
259    }
260
261    /// Append a new segment to the final [`EventType`] and return
262    /// the builder.
263    #[inline]
264    pub fn with_segment(mut self, segment: impl AsRef<str>) -> Self {
265        self.append_segment(segment);
266        self
267    }
268
269    /// Build the final [`EventType`].
270    #[inline]
271    pub fn build(self) -> EventType {
272        EventType {
273            inner: Cow::Owned(self.inner),
274        }
275    }
276}
277
278/// Custom events that can be queried from Tendermint
279/// using a websocket client
280#[derive(
281    Clone,
282    Debug,
283    Eq,
284    PartialEq,
285    Ord,
286    PartialOrd,
287    Hash,
288    BorshSerialize,
289    BorshDeserialize,
290    BorshDeserializer,
291    Serialize,
292    Deserialize,
293)]
294pub struct Event {
295    /// The level of the event - whether it relates to a block or an individual
296    /// transaction.
297    level: EventLevel,
298    /// The type of event.
299    event_type: EventType,
300    /// Key-value attributes of the event.
301    attributes: BTreeMap<String, String>,
302}
303
304/// Errors to do with emitting events.
305#[derive(Error, Debug, Clone)]
306pub enum EventError {
307    /// Invalid event domain.
308    #[error("Invalid event domain: {0}")]
309    InvalidDomain(String),
310    /// Missing event domain.
311    #[error("Missing the domain of the event")]
312    MissingDomain,
313    /// Error resulting from a missing event attribute.
314    #[error("Missing event attribute {0:?}")]
315    MissingAttribute(&'static str),
316    /// Error resulting from an invalid encoding of an event attribute.
317    #[error("Failed to parse event attribute: {0}")]
318    AttributeEncoding(String),
319    /// Error when parsing an event type
320    #[error("Invalid event type")]
321    InvalidEventType,
322    /// Error when parsing attributes from an event JSON.
323    #[error("Json missing `attributes` field")]
324    MissingAttributes,
325    /// Missing key in attributes.
326    #[error("Attributes missing key: {0}")]
327    MissingKey(String),
328    /// Missing value in attributes.
329    #[error("Attributes missing value: {0}")]
330    MissingValue(String),
331}
332
333impl Event {
334    /// Create a new event with no attributes and the given parameters.
335    pub fn new(event_type: EventType, level: EventLevel) -> Self {
336        Self {
337            event_type,
338            level,
339            attributes: BTreeMap::new(),
340        }
341    }
342
343    /// Return the level of the event.
344    #[inline]
345    pub fn level(&self) -> &EventLevel {
346        &self.level
347    }
348
349    /// Return the type of the event.
350    #[inline]
351    pub fn kind(&self) -> &EventType {
352        &self.event_type
353    }
354
355    /// Return a reference to the event's attributes.
356    #[deprecated = "Accessing the event attributes directly is deprecated. \
357                    Consider using domain types to compose events with \
358                    attributes."]
359    #[inline]
360    pub fn attributes(&self) -> &BTreeMap<String, String> {
361        &self.attributes
362    }
363
364    /// Return a mutable reference to the event's attributes.
365    #[deprecated = "Accessing the event attributes directly is deprecated. \
366                    Consider using domain types to compose events with \
367                    attributes."]
368    #[inline]
369    pub fn attributes_mut(&mut self) -> &mut BTreeMap<String, String> {
370        &mut self.attributes
371    }
372
373    /// Return the attributes of the event, destroying
374    /// it in the process.
375    #[inline]
376    pub fn into_attributes(self) -> BTreeMap<String, String> {
377        self.attributes
378    }
379
380    /// Check if this [`Event`] has a subset of the keys and values
381    /// in `attrs`.
382    #[inline]
383    pub fn has_subset_of_attrs<A: extend::AttributesMap>(
384        &self,
385        attrs: &A,
386    ) -> bool {
387        attrs.iter_attributes().all(|(key, value)| {
388            match self.attributes.get(key) {
389                Some(v) => v == value,
390                None => false,
391            }
392        })
393    }
394
395    /// Delete the given attribute.
396    #[inline]
397    pub fn delete_attribute<DATA>(&mut self)
398    where
399        DATA: extend::DeleteFromEventAttributes,
400    {
401        DATA::delete_from_event_attributes(&mut self.attributes);
402    }
403
404    /// Get the raw string value corresponding to a given attribute, if it
405    /// exists.
406    #[inline]
407    pub fn raw_read_attribute<'value, DATA>(&self) -> Option<&str>
408    where
409        DATA: extend::RawReadFromEventAttributes<'value>,
410    {
411        DATA::raw_read_opt_from_event_attributes(&self.attributes)
412    }
413
414    /// Get the value corresponding to a given attribute.
415    #[inline]
416    pub fn read_attribute<'value, DATA>(
417        &self,
418    ) -> Result<
419        <DATA as extend::ReadFromEventAttributes<'value>>::Value,
420        EventError,
421    >
422    where
423        DATA: extend::ReadFromEventAttributes<'value>,
424    {
425        DATA::read_from_event_attributes(&self.attributes)
426    }
427
428    /// Get the value corresponding to a given attribute, if it exists.
429    #[inline]
430    pub fn read_attribute_opt<'value, DATA>(
431        &self,
432    ) -> Result<
433        Option<<DATA as extend::ReadFromEventAttributes<'value>>::Value>,
434        EventError,
435    >
436    where
437        DATA: extend::ReadFromEventAttributes<'value>,
438    {
439        DATA::read_opt_from_event_attributes(&self.attributes)
440    }
441
442    /// Check if a certain attribute is present in the event.
443    #[inline]
444    pub fn has_attribute<'value, DATA>(&self) -> bool
445    where
446        DATA: extend::RawReadFromEventAttributes<'value>,
447    {
448        DATA::check_if_present_in(&self.attributes)
449    }
450
451    /// Extend this [`Event`] with additional data.
452    #[inline]
453    pub fn extend<DATA>(&mut self, data: DATA) -> &mut Self
454    where
455        DATA: extend::ExtendEvent,
456    {
457        data.extend_event(self);
458        self
459    }
460
461    /// Compute the gas cost of emitting this event. Returns `None` on u64
462    /// overflow.
463    #[inline]
464    pub fn emission_gas_cost(&self, cost_per_byte: u64) -> Option<u64> {
465        let len = self.attributes.iter().try_fold(0_usize, |acc, (k, v)| {
466            acc.checked_add(k.len())
467                .and_then(|val| val.checked_add(v.len()))
468        })?;
469        (len as u64).checked_mul(cost_per_byte)
470    }
471
472    /// Joins the attribute sets of two [`Event`]
473    pub fn merge(&mut self, other: Self) {
474        self.attributes.extend(other.attributes)
475    }
476}
477
478impl From<Event> for namada_core::tendermint_proto::abci::Event {
479    fn from(event: Event) -> Self {
480        Self {
481            r#type: {
482                use extend::{Domain, RawReadFromEventAttributes};
483
484                if Domain::<Event>::check_if_present_in(&event.attributes) {
485                    // NB: encode the domain of the event in the attributes.
486                    // this is necessary for ibc events, as hermes is not
487                    // compatible with our event type format.
488                    event.event_type.sub_domain().to_string()
489                } else {
490                    event.event_type.to_string()
491                }
492            },
493            attributes: event
494                .attributes
495                .into_iter()
496                .map(|(key, value)| {
497                    namada_core::tendermint_proto::abci::EventAttribute {
498                        key,
499                        value,
500                        index: true,
501                    }
502                })
503                .chain(std::iter::once_with(|| {
504                    namada_core::tendermint_proto::abci::EventAttribute {
505                        key: "event-level".to_string(),
506                        value: event.level.to_string(),
507                        index: true,
508                    }
509                }))
510                .collect(),
511        }
512    }
513}
514
515impl From<Event> for namada_core::tendermint::abci::Event {
516    fn from(event: Event) -> Self {
517        Self {
518            kind: {
519                use extend::{Domain, RawReadFromEventAttributes};
520
521                if Domain::<Event>::check_if_present_in(&event.attributes) {
522                    // NB: encode the domain of the event in the attributes.
523                    // this is necessary for ibc events, as hermes is not
524                    // compatible with our event type format.
525                    event.event_type.sub_domain().to_string()
526                } else {
527                    event.event_type.to_string()
528                }
529            },
530            attributes: event
531                .attributes
532                .into_iter()
533                .map(|(key, value)| (key, value, true).into())
534                .chain(std::iter::once_with(|| {
535                    ("event-level", event.level.to_string(), true).into()
536                }))
537                .collect(),
538        }
539    }
540}