celestia_core/abci/
event.rs

1use serde::Serialize;
2
3use crate::prelude::*;
4
5/// An event that occurred while processing a request.
6///
7/// Application developers can attach additional information to
8/// [`BeginBlock`](super::response::BeginBlock),
9/// [`EndBlock`](super::response::EndBlock),
10/// [`CheckTx`](super::response::CheckTx), and
11/// [`DeliverTx`](super::response::DeliverTx) responses. Later, transactions may
12/// be queried using these events.
13///
14/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#events)
15#[derive(Clone, PartialEq, Eq, Debug, Serialize, Hash)]
16pub struct Event {
17    /// The kind of event.
18    ///
19    /// Tendermint calls this the `type`, but we use `kind` to avoid confusion
20    /// with Rust types and follow Rust conventions.
21    pub kind: String,
22    /// A list of [`EventAttribute`]s describing the event.
23    pub attributes: Vec<EventAttribute>,
24}
25
26impl Event {
27    /// Construct an event from generic data.
28    ///
29    /// The `From` impls on [`EventAttribute`] and the [`EventAttributeIndexExt`]
30    /// trait allow ergonomic event construction, as in this example:
31    ///
32    /// ```
33    /// use celestia_tendermint::abci::{Event, EventAttributeIndexExt};
34    ///
35    /// let event = Event::new(
36    ///     "app",
37    ///     [
38    ///         ("key1", "value1").index(),
39    ///         ("key2", "value2").index(),
40    ///         ("key3", "value3").no_index(), // will not be indexed
41    ///     ],
42    /// );
43    /// ```
44    pub fn new<K, I>(kind: K, attributes: I) -> Self
45    where
46        K: Into<String>,
47        I: IntoIterator,
48        I::Item: Into<EventAttribute>,
49    {
50        Self {
51            kind: kind.into(),
52            attributes: attributes.into_iter().map(Into::into).collect(),
53        }
54    }
55
56    /// Checks whether `&self` is equal to `other`, ignoring the `index` field on any attributes.
57    pub fn eq_ignoring_index(&self, other: &Self) -> bool {
58        self.kind == other.kind
59            // IMPORTANT! We need to check the lengths before calling zip,
60            // in order to not drop any attributes.
61            && self.attributes.len() == other.attributes.len()
62            && self
63                .attributes
64                .iter()
65                .zip(other.attributes.iter())
66                .all(|(a, b)| a.eq_ignoring_index(b))
67    }
68
69    /// A variant of [`core::hash::Hash::hash`] that ignores the `index` field on any attributes.
70    pub fn hash_ignoring_index<H: core::hash::Hasher>(&self, state: &mut H) {
71        use core::hash::Hash;
72        self.kind.hash(state);
73        // We can't forward to the slice impl here, because we need to call `hash_ignoring_index`
74        // on each attribute, so we need to do our own length prefixing.
75        state.write_usize(self.attributes.len());
76        for attr in &self.attributes {
77            attr.hash_ignoring_index(state);
78        }
79    }
80}
81
82/// A marker trait for types that can be converted to and from [`Event`]s.
83///
84/// This trait doesn't make any assumptions about how the conversion is
85/// performed, or how the type's data is encoded in event attributes.  Instead,
86/// it just declares the conversion methods used to serialize the type to an
87/// [`Event`] and to deserialize it from an [`Event`], allowing downstream users
88/// to declare a single source of truth about how event data is structured.
89///
90/// # Contract
91///
92/// If `T: TypedEvent`, then:
93///
94/// - `T::try_from(e) == Ok(t)` for all `t: T, e: Event` where `Event::from(t).eq_ignoring_index(e)
95///   == true`.
96/// - `Event::from(T::try_from(e).unwrap()).eq_ignoring_index(e) == true` for all `e: Event` where
97///   `T::try_from(e)` returns `Ok(_)`.
98///
99/// In other words, the conversion methods should round-trip on the attributes,
100/// but are not required to preserve the (nondeterministic) index information.
101pub trait TypedEvent
102where
103    Self: TryFrom<Event>,
104    Event: From<Self>,
105{
106    /// Convenience wrapper around `Into::into` that doesn't require type inference.
107    fn into_event(self) -> Event {
108        self.into()
109    }
110}
111
112/// A key-value pair describing an [`Event`].
113///
114/// Generic methods are provided for more ergonomic attribute construction, see
115/// [`Event::new`] for details.
116///
117/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#events)
118#[derive(Clone, PartialEq, Eq, Debug, Serialize, Hash)]
119pub struct EventAttribute {
120    /// The event key.
121    pub key: String,
122    /// The event value.
123    pub value: String,
124    /// Whether Tendermint's indexer should index this event.
125    ///
126    /// **This field is nondeterministic**.
127    pub index: bool,
128}
129
130impl EventAttribute {
131    /// Checks whether `&self` is equal to `other`, ignoring the `index` field.
132    pub fn eq_ignoring_index(&self, other: &Self) -> bool {
133        self.key == other.key && self.value == other.value
134    }
135
136    /// A variant of [`core::hash::Hash::hash`] that ignores the `index` field.
137    pub fn hash_ignoring_index<H: core::hash::Hasher>(&self, state: &mut H) {
138        use core::hash::Hash;
139        // Call the `Hash` impl on the (k,v) tuple to avoid prefix collision issues.
140        (&self.key, &self.value).hash(state);
141    }
142}
143
144impl<K: Into<String>, V: Into<String>> From<(K, V, bool)> for EventAttribute {
145    fn from((key, value, index): (K, V, bool)) -> Self {
146        EventAttribute {
147            key: key.into(),
148            value: value.into(),
149            index,
150        }
151    }
152}
153
154/// Adds convenience methods to tuples for more ergonomic [`EventAttribute`]
155/// construction.
156///
157/// See [`Event::new`] for details.
158#[allow(missing_docs)]
159pub trait EventAttributeIndexExt: private::Sealed {
160    type Key;
161    type Value;
162
163    /// Indicate that this key/value pair should be indexed by Tendermint.
164    fn index(self) -> (Self::Key, Self::Value, bool);
165    /// Indicate that this key/value pair should not be indexed by Tendermint.
166    fn no_index(self) -> (Self::Key, Self::Value, bool);
167}
168
169impl<K: Into<String>, V: Into<String>> EventAttributeIndexExt for (K, V) {
170    type Key = K;
171    type Value = V;
172    fn index(self) -> (K, V, bool) {
173        let (key, value) = self;
174        (key, value, true)
175    }
176    fn no_index(self) -> (K, V, bool) {
177        let (key, value) = self;
178        (key, value, false)
179    }
180}
181
182mod private {
183    use crate::prelude::*;
184
185    pub trait Sealed {}
186
187    impl<K: Into<String>, V: Into<String>> Sealed for (K, V) {}
188}
189
190impl<K: Into<String>, V: Into<String>> From<(K, V)> for EventAttribute {
191    fn from((key, value): (K, V)) -> Self {
192        (key, value, false).into()
193    }
194}
195
196// =============================================================================
197// Protobuf conversions
198// =============================================================================
199
200mod v0_34 {
201    use super::{Event, EventAttribute};
202    use crate::prelude::*;
203    use core::convert::{TryFrom, TryInto};
204
205    use celestia_core_proto::v0_34::abci as pb;
206    use celestia_core_proto::Protobuf;
207
208    impl From<EventAttribute> for pb::EventAttribute {
209        fn from(event: EventAttribute) -> Self {
210            Self {
211                key: event.key.into(),
212                value: event.value.into(),
213                index: event.index,
214            }
215        }
216    }
217
218    impl TryFrom<pb::EventAttribute> for EventAttribute {
219        type Error = crate::Error;
220
221        fn try_from(event: pb::EventAttribute) -> Result<Self, Self::Error> {
222            // We insist that keys and values are strings, like tm 0.35 did.
223            Ok(Self {
224                key: String::from_utf8(event.key.to_vec())
225                    .map_err(|e| crate::Error::parse(e.to_string()))?,
226                value: String::from_utf8(event.value.to_vec())
227                    .map_err(|e| crate::Error::parse(e.to_string()))?,
228                index: event.index,
229            })
230        }
231    }
232
233    impl Protobuf<pb::EventAttribute> for EventAttribute {}
234
235    impl From<Event> for pb::Event {
236        fn from(event: Event) -> Self {
237            Self {
238                r#type: event.kind,
239                attributes: event.attributes.into_iter().map(Into::into).collect(),
240            }
241        }
242    }
243
244    impl TryFrom<pb::Event> for Event {
245        type Error = crate::Error;
246
247        fn try_from(event: pb::Event) -> Result<Self, Self::Error> {
248            Ok(Self {
249                kind: event.r#type,
250                attributes: event
251                    .attributes
252                    .into_iter()
253                    .map(TryInto::try_into)
254                    .collect::<Result<_, _>>()?,
255            })
256        }
257    }
258
259    impl Protobuf<pb::Event> for Event {}
260}
261
262#[cfg(test)]
263mod tests {
264    #![allow(clippy::bool_assert_comparison)]
265    #![allow(clippy::redundant_clone)]
266
267    use serde::Deserialize;
268
269    use super::*;
270
271    #[test]
272    fn event_eq_ignoring_index_ignores_index() {
273        let event_a = Event::new("test", [("foo", "bar").index()]);
274        let event_b = Event::new("test", [("foo", "bar").no_index()]);
275        let event_c = Event::new("test", [("foo", "baz").index()]);
276
277        assert_eq!(event_a.eq_ignoring_index(&event_b), true);
278        assert_eq!(event_a.eq_ignoring_index(&event_c), false);
279    }
280
281    #[test]
282    fn exercise_typed_event() {
283        #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
284        struct Payload {
285            x: u32,
286            y: u32,
287        }
288
289        #[derive(Clone, Debug, PartialEq, Eq)]
290        struct MyEvent {
291            a: Payload,
292            b: Payload,
293        }
294
295        impl From<MyEvent> for Event {
296            fn from(event: MyEvent) -> Self {
297                Event::new(
298                    "my_event",
299                    vec![
300                        ("a", serde_json::to_string(&event.a).unwrap()).index(),
301                        ("b", serde_json::to_string(&event.b).unwrap()).index(),
302                    ],
303                )
304            }
305        }
306
307        impl TryFrom<Event> for MyEvent {
308            type Error = (); // Avoid depending on a specific error library in test code
309
310            fn try_from(event: Event) -> Result<Self, Self::Error> {
311                if event.kind != "my_event" {
312                    return Err(());
313                }
314
315                let a = event
316                    .attributes
317                    .iter()
318                    .find(|attr| attr.key == "a")
319                    .ok_or(())
320                    .and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?;
321                let b = event
322                    .attributes
323                    .iter()
324                    .find(|attr| attr.key == "b")
325                    .ok_or(())
326                    .and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?;
327
328                Ok(MyEvent { a, b })
329            }
330        }
331
332        impl TypedEvent for MyEvent {}
333
334        let t = MyEvent {
335            a: Payload { x: 1, y: 2 },
336            b: Payload { x: 3, y: 4 },
337        };
338
339        let e1 = Event::from(t.clone());
340        // e2 is like e1 but with different indexing.
341        let e2 = {
342            let mut e = e1.clone();
343            e.attributes[0].index = false;
344            e.attributes[1].index = false;
345            e
346        };
347
348        // Contract:
349
350        // - `T::try_from(e) == Ok(t)` for all `t: T, e: Event` where
351        //   `Event::from(t).eq_ignoring_index(e) == true`.
352        assert_eq!(e1.eq_ignoring_index(&e2), true);
353        assert_eq!(MyEvent::try_from(e1.clone()), Ok(t.clone()));
354        assert_eq!(MyEvent::try_from(e2.clone()), Ok(t.clone()));
355
356        // - `Event::from(T::try_from(e).unwrap()).eq_ignoring_index(e) == true` for all `e: Event`
357        //   where `T::try_from(e)` returns `Ok(_)`.
358        assert_eq!(
359            Event::from(MyEvent::try_from(e1.clone()).unwrap()).eq_ignoring_index(&e1),
360            true
361        );
362        assert_eq!(
363            Event::from(MyEvent::try_from(e2.clone()).unwrap()).eq_ignoring_index(&e2),
364            true
365        );
366    }
367}