use std::collections::BTreeMap;
use dusk_core::signatures::bls::PublicKey as AccountPublicKey;
use dusk_core::transfer::withdraw::WithdrawReceiver;
use dusk_core::transfer::{
ContractToAccountEvent, ConvertEvent, MoonlightTransactionEvent,
WithdrawEvent, CONTRACT_TO_ACCOUNT_TOPIC, CONVERT_TOPIC, MINT_TOPIC,
MOONLIGHT_TOPIC, TRANSFER_CONTRACT, WITHDRAW_TOPIC,
};
use node_data::events::contract::{ContractEvent, ContractTxEvent, OriginHash};
use serde::{Deserialize, Serialize};
#[serde_with::serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(super) struct MoonlightTransferEvents {
events: Vec<ContractEvent>,
}
impl MoonlightTransferEvents {
fn new(events: Vec<ContractEvent>) -> Self {
Self { events }
}
pub fn events(self) -> Vec<ContractEvent> {
self.events
}
}
#[serde_with::serde_as]
#[derive(
Debug,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Serialize,
Deserialize,
)]
pub struct EventIdentifier {
pub(super) block_height: u64,
#[serde_as(as = "serde_with::hex::Hex")]
pub(super) tx_hash: OriginHash,
}
impl EventIdentifier {
pub fn origin(&self) -> &OriginHash {
&self.tx_hash
}
pub fn block_height(&self) -> u64 {
self.block_height
}
}
pub(super) type AddressMapping = (AccountPublicKey, EventIdentifier);
pub(super) type MemoMapping = (Vec<u8>, EventIdentifier);
pub(super) struct MoonlightTransferMapping(
pub EventIdentifier,
pub MoonlightTransferEvents,
);
pub(super) struct TransormerResult {
pub address_outflow_mappings: Vec<AddressMapping>,
pub address_inflow_mappings: Vec<AddressMapping>,
pub memo_mappings: Vec<MemoMapping>,
pub moonlight_tx_mappings: Vec<MoonlightTransferMapping>,
}
pub(super) fn group_by_origins(
block_events: Vec<ContractTxEvent>,
block_height: u64,
) -> BTreeMap<EventIdentifier, Vec<ContractEvent>> {
let mut is_already_grouped: BTreeMap<EventIdentifier, Vec<ContractEvent>> =
BTreeMap::new();
for event in block_events {
let event_to_analyze = event.event;
is_already_grouped
.entry(EventIdentifier {
block_height,
tx_hash: event.origin,
})
.or_default()
.push(event_to_analyze);
}
is_already_grouped
}
pub(super) fn filter_and_convert(
grouped_events: BTreeMap<EventIdentifier, Vec<ContractEvent>>,
) -> TransormerResult {
let mut address_inflow_mappings: Vec<(AccountPublicKey, EventIdentifier)> =
vec![];
let mut address_outflow_mappings: Vec<(AccountPublicKey, EventIdentifier)> =
vec![];
let mut memo_mappings: Vec<(Vec<u8>, EventIdentifier)> = vec![];
let mut moonlight_tx_mappings = vec![];
for (tx_ident, group) in grouped_events {
let got_recorded = record_flows(
&mut address_inflow_mappings,
&mut address_outflow_mappings,
&mut memo_mappings,
tx_ident,
&group,
);
if got_recorded {
moonlight_tx_mappings.push(MoonlightTransferMapping(
tx_ident,
MoonlightTransferEvents::new(group),
));
}
}
TransormerResult {
address_outflow_mappings,
address_inflow_mappings,
memo_mappings,
moonlight_tx_mappings,
}
}
fn record_flows(
address_inflow_mappings: &mut Vec<(AccountPublicKey, EventIdentifier)>,
address_outflow_mappings: &mut Vec<(AccountPublicKey, EventIdentifier)>,
memo_mappings: &mut Vec<(Vec<u8>, EventIdentifier)>,
tx_ident: EventIdentifier,
group: &[ContractEvent],
) -> bool {
let mut handle_inflow = |key: AccountPublicKey| {
if !address_inflow_mappings.contains(&(key, tx_ident)) {
address_inflow_mappings.push((key, tx_ident));
}
};
let mut handle_outflow = |key: AccountPublicKey| {
if !address_outflow_mappings.contains(&(key, tx_ident)) {
address_outflow_mappings.push((key, tx_ident));
}
};
let filtered_group = group
.iter()
.filter(|event| {
if event.target != TRANSFER_CONTRACT {
return false;
}
match event.topic.as_str() {
MOONLIGHT_TOPIC => {
let Ok(moonlight_event) = rkyv::from_bytes::<
MoonlightTransactionEvent,
>(&event.data) else {
return false;
};
handle_outflow(moonlight_event.sender);
match (
moonlight_event.receiver,
moonlight_event.refund_info,
) {
(None, refund) => {
if group.len() == 1 {
handle_inflow(moonlight_event.sender);
}
if let Some((key, amt)) = refund {
if amt > 0 {
handle_inflow(key);
}
}
}
(Some(receiver), None) => {
handle_inflow(receiver);
}
(Some(receiver), Some((key, amt))) => {
handle_inflow(receiver);
if amt > 0 && key != receiver
{
handle_inflow(key);
}
}
}
if !moonlight_event.memo.is_empty() {
memo_mappings.push((moonlight_event.memo, tx_ident));
}
true
}
CONVERT_TOPIC => {
let Ok(convert_event) =
rkyv::from_bytes::<ConvertEvent>(&event.data)
else {
return false;
};
let WithdrawReceiver::Moonlight(key) =
convert_event.receiver
else {
return false;
};
handle_inflow(key);
true
}
WITHDRAW_TOPIC | MINT_TOPIC => {
let Ok(withdraw_event) =
rkyv::from_bytes::<WithdrawEvent>(&event.data)
else {
return false;
};
let WithdrawReceiver::Moonlight(key) =
withdraw_event.receiver
else {
return false;
};
handle_inflow(key);
true
}
CONTRACT_TO_ACCOUNT_TOPIC => {
let Ok(contract_to_account_event) =
rkyv::from_bytes::<ContractToAccountEvent>(&event.data)
else {
return false;
};
handle_inflow(contract_to_account_event.receiver);
true
}
_ => false,
}
})
.collect::<Vec<&ContractEvent>>();
!filtered_group.is_empty()
}