use std::collections::BTreeSet;
use std::ops::Deref;
use namada_core::address::Address;
use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use namada_core::chain::BlockHeight;
use namada_core::collections::HashMap;
use namada_core::ethereum_events::EthereumEvent;
use namada_core::key::common::{self, Signature};
use namada_macros::BorshDeserializer;
#[cfg(feature = "migrations")]
use namada_migrations::*;
use namada_tx::Signed;
pub type Vext = EthereumEventsVext;
#[derive(
Clone,
Debug,
BorshSerialize,
BorshSchema,
BorshDeserialize,
BorshDeserializer,
)]
pub struct SignedVext(pub Signed<Vext>);
impl Deref for SignedVext {
type Target = Signed<Vext>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Signed<Vext>> for SignedVext {
fn from(value: Signed<Vext>) -> Self {
Self(value)
}
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct EthereumEventsVext {
pub block_height: BlockHeight,
pub validator_addr: Address,
pub ethereum_events: Vec<EthereumEvent>,
}
impl Vext {
pub fn empty(block_height: BlockHeight, validator_addr: Address) -> Self {
Self {
block_height,
ethereum_events: Vec::new(),
validator_addr,
}
}
pub fn sign(self, signing_key: &common::SecretKey) -> Signed<Self> {
Signed::new(signing_key, self)
}
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct MultiSignedEthEvent {
pub event: EthereumEvent,
pub signers: BTreeSet<(Address, BlockHeight)>,
}
pub type VextDigest = EthereumEventsVextDigest;
#[derive(
Debug,
Clone,
PartialEq,
Eq,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct EthereumEventsVextDigest {
pub signatures: HashMap<(Address, BlockHeight), Signature>,
pub events: Vec<MultiSignedEthEvent>,
}
impl VextDigest {
#[inline]
pub fn singleton(ext: Signed<Vext>) -> VextDigest {
VextDigest {
signatures: HashMap::from([(
(ext.data.validator_addr.clone(), ext.data.block_height),
ext.sig,
)]),
events: ext
.data
.ethereum_events
.into_iter()
.map(|event| MultiSignedEthEvent {
event,
signers: BTreeSet::from([(
ext.data.validator_addr.clone(),
ext.data.block_height,
)]),
})
.collect(),
}
}
pub fn decompress(self, _: BlockHeight) -> Vec<Signed<Vext>> {
let VextDigest { signatures, events } = self;
let mut extensions = vec![];
for (validator, sig) in signatures.into_iter() {
let mut ext = Vext::empty(validator.1, validator.0.clone());
for event in events.iter() {
if event.signers.contains(&validator) {
ext.ethereum_events.push(event.event.clone());
}
}
ext.ethereum_events.sort();
let signed = Signed::new_from(ext, sig);
extensions.push(signed);
}
extensions
}
}
#[cfg(test)]
mod tests {
use namada_core::ethereum_events::Uint;
use namada_core::hash::Hash;
use namada_core::{address, key};
use super::*;
#[test]
fn test_ethereum_event_hash() {
let nonce = Uint::from(123u64);
let event = EthereumEvent::TransfersToNamada {
nonce,
transfers: vec![],
};
let hash = event.hash().unwrap();
assert_eq!(
hash,
Hash([
94, 131, 116, 129, 41, 204, 178, 144, 24, 8, 185, 16, 103, 236,
209, 191, 20, 89, 145, 17, 41, 233, 31, 98, 185, 6, 217, 204,
80, 38, 224, 23
])
);
}
#[test]
fn test_decompress_ethereum_events() {
let sk_1 = key::testing::keypair_1();
let sk_2 = key::testing::keypair_2();
let last_block_height = BlockHeight(123);
let ev_1 = EthereumEvent::TransfersToNamada {
nonce: 1u64.into(),
transfers: vec![],
};
let ev_2 = EthereumEvent::TransfersToEthereum {
nonce: 2u64.into(),
transfers: vec![],
relayer: address::testing::established_address_1(),
};
let validator_1 = address::testing::established_address_1();
let validator_2 = address::testing::established_address_2();
let ext = |validator: Address| -> Vext {
let mut ext = Vext::empty(last_block_height, validator);
ext.ethereum_events.push(ev_1.clone());
ext.ethereum_events.push(ev_2.clone());
ext.ethereum_events.sort();
ext
};
let ext_1 = Signed::new(&sk_1, ext(validator_1.clone()));
let ext_2 = Signed::new(&sk_2, ext(validator_2.clone()));
let ext_3 = Signed::new(&sk_1, {
let mut ext = Vext::empty(
BlockHeight(last_block_height.0 - 1),
validator_1.clone(),
);
ext.ethereum_events.push(ev_1.clone());
ext.ethereum_events.push(ev_2.clone());
ext.ethereum_events.sort();
ext
});
let ext = vec![ext_1, ext_2, ext_3];
let signatures: HashMap<_, _> = [
((validator_1.clone(), last_block_height), ext[0].sig.clone()),
((validator_2.clone(), last_block_height), ext[1].sig.clone()),
(
(validator_1.clone(), BlockHeight(last_block_height.0 - 1)),
ext[2].sig.clone(),
),
]
.into_iter()
.collect();
let signers = {
let mut s = BTreeSet::new();
s.insert((validator_1.clone(), last_block_height));
s.insert((
validator_1.clone(),
BlockHeight(last_block_height.0 - 1),
));
s.insert((validator_2, last_block_height));
s
};
let events = vec![
MultiSignedEthEvent {
event: ev_1,
signers: signers.clone(),
},
MultiSignedEthEvent {
event: ev_2,
signers,
},
];
let digest = VextDigest { events, signatures };
let decompressed = digest
.decompress(last_block_height)
.into_iter()
.collect::<Vec<Signed<Vext>>>();
assert_eq!(decompressed.len(), ext.len());
for vext in decompressed.into_iter() {
assert!(ext.contains(&vext));
if vext.data.validator_addr == validator_1 {
assert!(vext.verify(&sk_1.to_public()).is_ok())
} else {
assert!(vext.verify(&sk_2.to_public()).is_ok())
}
}
}
}