1use crate::{DomainType, Message, Name};
2use anyhow::{self, Context};
3use serde::{de::DeserializeOwned, Serialize};
4use std::collections::HashMap;
5use tendermint::abci::{self, EventAttribute};
6
7pub trait ProtoEvent: Message + Name + Serialize + DeserializeOwned + Sized {
8 fn into_event(&self) -> abci::Event {
9 let kind = Self::full_name();
10
11 let event_json = serde_json::to_value(&self)
12 .expect("ProtoEvent constrained values should be JSON serializable.");
13
14 let mut attributes: Vec<EventAttribute> = event_json
16 .as_object()
17 .expect("serde_json Serialized ProtoEvent should not be empty.")
18 .into_iter()
19 .map(|(key, v)| {
20 abci::EventAttribute::V037(abci::v0_37::EventAttribute {
21 value: serde_json::to_string(v)
22 .expect("must be able to serialize value as JSON"),
23 key: key.to_string(),
24 index: true,
25 })
26 })
27 .collect();
28
29 attributes.sort_by(|a, b| (&a.key_bytes()).cmp(&b.key_bytes()));
32
33 return abci::Event::new(kind, attributes);
34 }
35
36 fn from_event(event: &abci::Event) -> anyhow::Result<Self> {
37 if Self::full_name() != event.kind {
39 return Err(anyhow::anyhow!(format!(
40 "ABCI Event {} not expected for {}",
41 event.kind,
42 Self::full_name()
43 )));
44 }
45
46 let mut attributes = HashMap::<String, serde_json::Value>::new();
48 for attr in &event.attributes {
49 let value = serde_json::from_slice(attr.value_bytes())
50 .with_context(|| format!("could not parse JSON for attribute {:?}", attr))?;
51 attributes.insert(String::from_utf8_lossy(attr.key_bytes()).into(), value);
52 }
53
54 let json = serde_json::to_value(attributes)
55 .expect("HashMap of String, serde_json::Value should be serializeable.");
56
57 return Ok(
58 serde_json::from_value(json).context("could not deserialise ProtoJSON into event")?
59 );
60 }
61}
62
63impl<E: Message + Name + Serialize + DeserializeOwned + Sized> ProtoEvent for E {}
64
65#[cfg(test)]
66mod tests {
67 #[test]
68 fn event_round_trip() {
69 use super::*;
70 use crate::core::component::sct::v1::Nullifier;
71 use crate::core::component::shielded_pool::v1::{EventOutput, EventSpend};
72 use crate::crypto::tct::v1::StateCommitment;
73
74 let proto_spend = EventSpend {
75 nullifier: Some(Nullifier {
76 inner: vec![
77 148, 190, 149, 23, 86, 113, 152, 145, 104, 242, 142, 162, 233, 239, 137, 141,
78 140, 164, 180, 98, 154, 55, 168, 255, 163, 228, 179, 176, 26, 25, 219, 211,
79 ],
80 }),
81 };
82
83 let abci_spend = proto_spend.into_event();
84
85 let expected_abci_spend = abci::Event::new(
86 "penumbra.core.component.shielded_pool.v1.EventSpend",
87 [abci::EventAttribute::V037(abci::v0_37::EventAttribute {
88 key: "nullifier".to_string(),
89 value: "{\"inner\":\"lL6VF1ZxmJFo8o6i6e+JjYyktGKaN6j/o+SzsBoZ29M=\"}".to_string(),
90 index: true,
91 })],
92 );
93 assert_eq!(abci_spend, expected_abci_spend);
94
95 let proto_spend2 = EventSpend::from_event(&abci_spend).unwrap();
96
97 assert_eq!(proto_spend, proto_spend2);
98
99 let proto_output = EventOutput {
100 note_commitment: Some(StateCommitment {
102 inner: vec![
103 148, 190, 149, 23, 86, 113, 152, 145, 104, 242, 142, 162, 233, 239, 137, 141,
104 140, 164, 180, 98, 154, 55, 168, 255, 163, 228, 179, 176, 26, 25, 219, 211,
105 ],
106 }),
107 };
108
109 let abci_output = proto_output.into_event();
110
111 let expected_abci_output = abci::Event::new(
112 "penumbra.core.component.shielded_pool.v1.EventOutput",
113 [abci::EventAttribute::V037(abci::v0_37::EventAttribute {
114 key: "noteCommitment".to_string(),
116 value: "{\"inner\":\"lL6VF1ZxmJFo8o6i6e+JjYyktGKaN6j/o+SzsBoZ29M=\"}".to_string(),
118 index: true,
119 })],
120 );
121 assert_eq!(abci_output, expected_abci_output);
122
123 let proto_output2 = EventOutput::from_event(&abci_output).unwrap();
124 assert_eq!(proto_output, proto_output2);
125 }
126}
127
128pub trait EventDomainType: DomainType
134where
135 <Self as DomainType>::Proto: ProtoEvent,
136 anyhow::Error: From<<Self as TryFrom<<Self as DomainType>::Proto>>::Error>,
137{
138 fn try_from_event(event: &abci::Event) -> anyhow::Result<Self> {
139 Ok(<Self as DomainType>::Proto::from_event(event)?.try_into()?)
140 }
141}
142
143impl<T: DomainType> EventDomainType for T
144where
145 <T as DomainType>::Proto: ProtoEvent,
146 anyhow::Error: From<<Self as TryFrom<<Self as DomainType>::Proto>>::Error>,
147{
148}