use crate::{DomainType, Message, Name};
use anyhow::{self, Context};
use serde::{de::DeserializeOwned, Serialize};
use std::collections::HashMap;
use tendermint::abci::{self, EventAttribute};
pub trait ProtoEvent: Message + Name + Serialize + DeserializeOwned + Sized {
fn into_event(&self) -> abci::Event {
let kind = Self::full_name();
let event_json = serde_json::to_value(&self)
.expect("ProtoEvent constrained values should be JSON serializable.");
let mut attributes: Vec<EventAttribute> = event_json
.as_object()
.expect("serde_json Serialized ProtoEvent should not be empty.")
.into_iter()
.map(|(key, v)| abci::EventAttribute {
value: serde_json::to_string(v).expect("must be able to serialize value as JSON"),
key: key.to_string(),
index: true,
})
.collect();
attributes.sort_by(|a, b| (&a.key).cmp(&b.key));
return abci::Event::new(kind, attributes);
}
fn from_event(event: &abci::Event) -> anyhow::Result<Self> {
if Self::full_name() != event.kind {
return Err(anyhow::anyhow!(format!(
"ABCI Event {} not expected for {}",
event.kind,
Self::full_name()
)));
}
let mut attributes = HashMap::<String, serde_json::Value>::new();
for attr in &event.attributes {
let value = serde_json::from_str(&attr.value)
.with_context(|| format!("could not parse JSON for attribute {:?}", attr))?;
attributes.insert(attr.key.clone(), value);
}
let json = serde_json::to_value(attributes)
.expect("HashMap of String, serde_json::Value should be serializeable.");
return Ok(
serde_json::from_value(json).context("could not deserialise ProtoJSON into event")?
);
}
}
impl<E: Message + Name + Serialize + DeserializeOwned + Sized> ProtoEvent for E {}
#[cfg(test)]
mod tests {
#[test]
fn event_round_trip() {
use super::*;
use crate::core::component::sct::v1::Nullifier;
use crate::core::component::shielded_pool::v1::{EventOutput, EventSpend};
use crate::crypto::tct::v1::StateCommitment;
let proto_spend = EventSpend {
nullifier: Some(Nullifier {
inner: vec![
148, 190, 149, 23, 86, 113, 152, 145, 104, 242, 142, 162, 233, 239, 137, 141,
140, 164, 180, 98, 154, 55, 168, 255, 163, 228, 179, 176, 26, 25, 219, 211,
],
}),
};
let abci_spend = proto_spend.into_event();
let expected_abci_spend = abci::Event::new(
"penumbra.core.component.shielded_pool.v1.EventSpend",
vec![abci::EventAttribute {
key: "nullifier".to_string(),
value: "{\"inner\":\"lL6VF1ZxmJFo8o6i6e+JjYyktGKaN6j/o+SzsBoZ29M=\"}".to_string(),
index: true,
}],
);
assert_eq!(abci_spend, expected_abci_spend);
let proto_spend2 = EventSpend::from_event(&abci_spend).unwrap();
assert_eq!(proto_spend, proto_spend2);
let proto_output = EventOutput {
note_commitment: Some(StateCommitment {
inner: vec![
148, 190, 149, 23, 86, 113, 152, 145, 104, 242, 142, 162, 233, 239, 137, 141,
140, 164, 180, 98, 154, 55, 168, 255, 163, 228, 179, 176, 26, 25, 219, 211,
],
}),
};
let abci_output = proto_output.into_event();
let expected_abci_output = abci::Event::new(
"penumbra.core.component.shielded_pool.v1.EventOutput",
vec![abci::EventAttribute {
key: "noteCommitment".to_string(),
value: "{\"inner\":\"lL6VF1ZxmJFo8o6i6e+JjYyktGKaN6j/o+SzsBoZ29M=\"}".to_string(),
index: true,
}],
);
assert_eq!(abci_output, expected_abci_output);
let proto_output2 = EventOutput::from_event(&abci_output).unwrap();
assert_eq!(proto_output, proto_output2);
}
}
pub trait EventDomainType: DomainType
where
<Self as DomainType>::Proto: ProtoEvent,
anyhow::Error: From<<Self as TryFrom<<Self as DomainType>::Proto>>::Error>,
{
fn try_from_event(event: &abci::Event) -> anyhow::Result<Self> {
Ok(<Self as DomainType>::Proto::from_event(event)?.try_into()?)
}
}
impl<T: DomainType> EventDomainType for T
where
<T as DomainType>::Proto: ProtoEvent,
anyhow::Error: From<<Self as TryFrom<<Self as DomainType>::Proto>>::Error>,
{
}