From 104e6a74eaf6050f855161b0bdcf4f9842122886 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Wed, 15 Jan 2025 14:04:50 +0100 Subject: [PATCH 1/8] added better transfer classification --- .../down.sql | 2 + .../up.sql | 2 + orm/src/transactions.rs | 6 + shared/src/block.rs | 125 +++++++++++++++++- shared/src/ser.rs | 6 +- shared/src/transaction.rs | 75 +++++++++-- transactions/src/services/tx.rs | 15 ++- webserver/src/response/transaction.rs | 2 + 8 files changed, 207 insertions(+), 26 deletions(-) create mode 100644 orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/down.sql create mode 100644 orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/up.sql diff --git a/orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/down.sql b/orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/down.sql new file mode 100644 index 000000000..c7c9cbeb4 --- /dev/null +++ b/orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/up.sql b/orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/up.sql new file mode 100644 index 000000000..76cfe3b6f --- /dev/null +++ b/orm/migrations/2025-01-15-130138_transaction_kind_mixed_transfer/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TYPE TRANSACTION_KIND ADD VALUE 'mixed_transfer'; \ No newline at end of file diff --git a/orm/src/transactions.rs b/orm/src/transactions.rs index 1509ead1c..599bab567 100644 --- a/orm/src/transactions.rs +++ b/orm/src/transactions.rs @@ -16,6 +16,7 @@ pub enum TransactionKindDb { ShieldedTransfer, ShieldingTransfer, UnshieldingTransfer, + MixedTransfer, IbcMsgTransfer, Bond, Redelegation, @@ -41,6 +42,11 @@ impl From for TransactionKindDb { Self::TransparentTransfer } TransactionKind::ShieldedTransfer(_) => Self::ShieldedTransfer, + TransactionKind::UnshieldingTransfer(_) => { + Self::UnshieldingTransfer + } + TransactionKind::ShieldingTransfer(_) => Self::ShieldingTransfer, + TransactionKind::MixedTransfer(_) => Self::MixedTransfer, TransactionKind::IbcMsgTransfer(_) => Self::IbcMsgTransfer, TransactionKind::Bond(_) => Self::Bond, TransactionKind::Redelegation(_) => Self::Redelegation, diff --git a/shared/src/block.rs b/shared/src/block.rs index 24f0b7f4e..30d78376c 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -5,7 +5,7 @@ use namada_ibc::apps::transfer::types::packet::PacketData; use namada_ibc::core::channel::types::msgs::{MsgRecvPacket, PacketMsg}; use namada_ibc::core::handler::types::msgs::MsgEnvelope; use namada_ibc::IbcMessage; -use namada_sdk::address::Address; +use namada_sdk::address::{Address, InternalAddress}; use namada_sdk::borsh::BorshDeserialize; use namada_sdk::token::Transfer; use subtle_encoding::hex; @@ -112,13 +112,12 @@ impl Block { pub fn from( block_response: &TendermintBlockResponse, block_results: &BlockResult, - proposer_address_namada: &Option, /* Provide the namada address - * of the proposer, if - * available */ + proposer_address_namada: &Option, checksums: Checksums, epoch: Epoch, block_height: BlockHeight, ) -> Self { + let masp_address = Address::Internal(InternalAddress::Masp); let transactions = block_response .block .data @@ -131,6 +130,7 @@ impl Block { block_height, checksums.clone(), block_results, + &masp_address, ) .map_err(|reason| { tracing::info!("Couldn't deserialize tx due to {}", reason); @@ -208,8 +208,121 @@ impl Block { vec![] } } - TransactionKind::ShieldedTransfer(_shielded_transfer) => { - vec![] + TransactionKind::MixedTransfer(transparent_transfer) => { + if let Some(data) = transparent_transfer { + let sources = data + .sources + .0 + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = data + .targets + .0 + .keys() + .map(|account| { + TransactionTarget::received( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } else { + vec![] + } + } + TransactionKind::ShieldedTransfer(transparent_transfer) => { + if let Some(data) = transparent_transfer { + let sources = data + .sources + .0 + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = data + .targets + .0 + .keys() + .map(|account| { + TransactionTarget::received( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } else { + vec![] + } + } + TransactionKind::UnshieldingTransfer(transparent_transfer) => { + if let Some(data) = transparent_transfer { + let sources = data + .sources + .0 + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = data + .targets + .0 + .keys() + .map(|account| { + TransactionTarget::received( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } else { + vec![] + } + } + TransactionKind::ShieldingTransfer(transparent_transfer) => { + if let Some(data) = transparent_transfer { + let sources = data + .sources + .0 + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = data + .targets + .0 + .keys() + .map(|account| { + TransactionTarget::received( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } else { + vec![] + } } TransactionKind::IbcMsgTransfer(ibc_message) => { if let Some(data) = ibc_message { diff --git a/shared/src/ser.rs b/shared/src/ser.rs index a9e9ee609..27ef6588b 100644 --- a/shared/src/ser.rs +++ b/shared/src/ser.rs @@ -68,7 +68,7 @@ impl<'de> Deserialize<'de> for AccountsMap { } #[derive(Deserialize, Serialize, Debug, Clone)] -pub struct TransparentTransfer { +pub struct TransferData { /// Sources of this transfer pub sources: AccountsMap, /// Targets of this transfer @@ -77,13 +77,13 @@ pub struct TransparentTransfer { pub shielded_section_hash: Option, } -impl From for TransparentTransfer { +impl From for TransferData { fn from(transfer: NamadaTransfer) -> Self { let sources = AccountsMap(transfer.sources); let targets = AccountsMap(transfer.targets); let shielded_section_hash = transfer.shielded_section_hash; - TransparentTransfer { + TransferData { sources, targets, shielded_section_hash, diff --git a/shared/src/transaction.rs b/shared/src/transaction.rs index 5ab4ad344..bb5d73058 100644 --- a/shared/src/transaction.rs +++ b/shared/src/transaction.rs @@ -5,7 +5,6 @@ use namada_governance::{InitProposalData, VoteProposalData}; use namada_sdk::address::Address; use namada_sdk::borsh::BorshDeserialize; use namada_sdk::key::common::PublicKey; -use namada_sdk::masp::ShieldedTransfer; use namada_sdk::token::Transfer; use namada_sdk::uint::Uint; use namada_tx::data::pos::{ @@ -21,7 +20,7 @@ use crate::block::BlockHeight; use crate::block_result::{BlockResult, TxEventStatusCode}; use crate::checksums::Checksums; use crate::id::Id; -use crate::ser::{IbcMessage, TransparentTransfer}; +use crate::ser::{IbcMessage, TransferData}; // We wrap public key in a struct so we serialize data as object instead of // string @@ -33,10 +32,11 @@ pub struct RevealPkData { #[derive(Serialize, Debug, Clone)] #[serde(untagged)] pub enum TransactionKind { - TransparentTransfer(Option), - // TODO: remove once ShieldedTransfer can be serialized - #[serde(skip)] - ShieldedTransfer(Option), + TransparentTransfer(Option), + ShieldedTransfer(Option), + ShieldingTransfer(Option), + UnshieldingTransfer(Option), + MixedTransfer(Option), IbcMsgTransfer(Option>), Bond(Option), Redelegation(Option), @@ -60,16 +60,60 @@ impl TransactionKind { serde_json::to_string(&self).ok() } - pub fn from(tx_kind_name: &str, data: &[u8]) -> Self { + pub fn from( + tx_kind_name: &str, + data: &[u8], + masp_address: &Address, + ) -> Self { match tx_kind_name { "tx_transfer" => { - let data = if let Ok(data) = Transfer::try_from_slice(data) { - Some(TransparentTransfer::from(data)) + if let Ok(data) = Transfer::try_from_slice(data) { + let has_shielded_section = + data.shielded_section_hash.is_some(); + let all_sources_are_masp = data + .sources + .iter() + .all(|(acc, _)| acc.owner.eq(masp_address)); + let any_sources_are_masp = data + .sources + .iter() + .all(|(acc, _)| acc.owner.eq(masp_address)); + let all_targets_are_masp = data + .targets + .iter() + .all(|(acc, _)| acc.owner.eq(masp_address)); + let any_targets_are_masp = data + .targets + .iter() + .all(|(acc, _)| acc.owner.eq(masp_address)); + if all_sources_are_masp + && all_targets_are_masp + && has_shielded_section + { + TransactionKind::ShieldedTransfer(Some(data.into())) + } else if all_sources_are_masp + && !any_targets_are_masp + && has_shielded_section + { + TransactionKind::UnshieldingTransfer(Some(data.into())) + } else if !any_sources_are_masp + && all_targets_are_masp + && has_shielded_section + { + TransactionKind::ShieldingTransfer(Some(data.into())) + } else if !any_sources_are_masp + && !any_targets_are_masp + && !has_shielded_section + { + TransactionKind::TransparentTransfer(Some(data.into())) + } else { + TransactionKind::MixedTransfer(Some(data.into())) + } } else { - None - }; - TransactionKind::TransparentTransfer(data) + TransactionKind::Unknown + } } + "tx_bond" => { let data = if let Ok(data) = Bond::try_from_slice(data) { Some(data) @@ -299,6 +343,7 @@ impl Transaction { block_height: BlockHeight, checksums: Checksums, block_results: &BlockResult, + masp_address: &Address, ) -> Result<(WrapperTransaction, Vec), String> { let transaction = Tx::try_from(raw_tx_bytes).map_err(|e| e.to_string())?; @@ -379,7 +424,11 @@ impl Transaction { if let Some(tx_kind_name) = checksums.get_name_by_id(&id) { - TransactionKind::from(&tx_kind_name, &tx_data) + TransactionKind::from( + &tx_kind_name, + &tx_data, + masp_address, + ) } else { TransactionKind::Unknown } diff --git a/transactions/src/services/tx.rs b/transactions/src/services/tx.rs index 2ed2645c7..7b56c4e57 100644 --- a/transactions/src/services/tx.rs +++ b/transactions/src/services/tx.rs @@ -133,12 +133,10 @@ pub fn get_gas_estimates( && inner_tx.wrapper_id.eq(&wrapper_tx.tx_id) }) .for_each(|tx| match tx.kind { - TransactionKind::TransparentTransfer(_) => { + TransactionKind::TransparentTransfer(_) + | TransactionKind::MixedTransfer(_) => { gas_estimate.increase_transparent_transfer() } - TransactionKind::ShieldedTransfer(_) => { - gas_estimate.increase_shielded_transfer() - } TransactionKind::IbcMsgTransfer(_) => { gas_estimate.increase_ibc_msg_transfer() } @@ -161,6 +159,15 @@ pub fn get_gas_estimates( TransactionKind::RevealPk(_) => { gas_estimate.increase_reveal_pk() } + TransactionKind::ShieldedTransfer(_) => { + gas_estimate.increase_shielded_transfer() + } + TransactionKind::ShieldingTransfer(_) => { + gas_estimate.increase_shielding_transfer() + } + TransactionKind::UnshieldingTransfer(_) => { + gas_estimate.increase_unshielding_transfer() + } _ => (), }); gas_estimate diff --git a/webserver/src/response/transaction.rs b/webserver/src/response/transaction.rs index 156161f48..eabff16ea 100644 --- a/webserver/src/response/transaction.rs +++ b/webserver/src/response/transaction.rs @@ -18,6 +18,7 @@ pub enum TransactionKind { ShieldedTransfer, ShieldingTransfer, UnshieldingTransfer, + MixedTransfer, Bond, Redelegation, Unbond, @@ -99,6 +100,7 @@ impl From for TransactionKind { TransactionKindDb::ShieldedTransfer => Self::ShieldedTransfer, TransactionKindDb::ShieldingTransfer => Self::ShieldingTransfer, TransactionKindDb::UnshieldingTransfer => Self::UnshieldingTransfer, + TransactionKindDb::MixedTransfer => Self::MixedTransfer, TransactionKindDb::Bond => Self::Bond, TransactionKindDb::Redelegation => Self::Redelegation, TransactionKindDb::Unbond => Self::Unbond, From 9c1430e24da585ef9dceb0445a33b2c58d197cbf Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Wed, 15 Jan 2025 14:35:49 +0100 Subject: [PATCH 2/8] refactor --- shared/src/transaction.rs | 73 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/shared/src/transaction.rs b/shared/src/transaction.rs index bb5d73058..c0554d311 100644 --- a/shared/src/transaction.rs +++ b/shared/src/transaction.rs @@ -70,44 +70,49 @@ impl TransactionKind { if let Ok(data) = Transfer::try_from_slice(data) { let has_shielded_section = data.shielded_section_hash.is_some(); - let all_sources_are_masp = data - .sources - .iter() - .all(|(acc, _)| acc.owner.eq(masp_address)); - let any_sources_are_masp = data + + let (all_sources_are_masp, any_sources_are_masp) = data .sources .iter() - .all(|(acc, _)| acc.owner.eq(masp_address)); - let all_targets_are_masp = data - .targets - .iter() - .all(|(acc, _)| acc.owner.eq(masp_address)); - let any_targets_are_masp = data + .fold((true, false), |(all, any), (acc, _)| { + let is_masp = acc.owner.eq(masp_address); + (all && is_masp, any || is_masp) + }); + + let (all_targets_are_masp, any_targets_are_masp) = data .targets .iter() - .all(|(acc, _)| acc.owner.eq(masp_address)); - if all_sources_are_masp - && all_targets_are_masp - && has_shielded_section - { - TransactionKind::ShieldedTransfer(Some(data.into())) - } else if all_sources_are_masp - && !any_targets_are_masp - && has_shielded_section - { - TransactionKind::UnshieldingTransfer(Some(data.into())) - } else if !any_sources_are_masp - && all_targets_are_masp - && has_shielded_section - { - TransactionKind::ShieldingTransfer(Some(data.into())) - } else if !any_sources_are_masp - && !any_targets_are_masp - && !has_shielded_section - { - TransactionKind::TransparentTransfer(Some(data.into())) - } else { - TransactionKind::MixedTransfer(Some(data.into())) + .fold((true, false), |(all, any), (acc, _)| { + let is_masp = acc.owner.eq(masp_address); + (all && is_masp, any || is_masp) + }); + + match ( + all_sources_are_masp, + any_sources_are_masp, + all_targets_are_masp, + any_targets_are_masp, + has_shielded_section, + ) { + (true, _, true, _, true) => { + TransactionKind::ShieldedTransfer(Some(data.into())) + } + (true, _, _, false, true) => { + TransactionKind::UnshieldingTransfer(Some( + data.into(), + )) + } + (false, _, true, _, true) => { + TransactionKind::ShieldingTransfer(Some( + data.into(), + )) + } + (false, _, false, _, false) => { + TransactionKind::TransparentTransfer(Some( + data.into(), + )) + } + _ => TransactionKind::MixedTransfer(Some(data.into())), } } else { TransactionKind::Unknown From 02cbda85915095916aebe57c394a91ee64d3b70a Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 16 Jan 2025 17:01:55 +0100 Subject: [PATCH 3/8] added ibc transfers --- .../down.sql | 2 + .../up.sql | 4 + orm/src/transactions.rs | 12 ++ shared/src/block.rs | 195 ++++++++++++++++++ shared/src/transaction.rs | 150 +++++++++++++- webserver/src/response/transaction.rs | 12 ++ 6 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 orm/migrations/2025-01-16-131336_transaction_ibc_types/down.sql create mode 100644 orm/migrations/2025-01-16-131336_transaction_ibc_types/up.sql diff --git a/orm/migrations/2025-01-16-131336_transaction_ibc_types/down.sql b/orm/migrations/2025-01-16-131336_transaction_ibc_types/down.sql new file mode 100644 index 000000000..c7c9cbeb4 --- /dev/null +++ b/orm/migrations/2025-01-16-131336_transaction_ibc_types/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/orm/migrations/2025-01-16-131336_transaction_ibc_types/up.sql b/orm/migrations/2025-01-16-131336_transaction_ibc_types/up.sql new file mode 100644 index 000000000..dd15f662a --- /dev/null +++ b/orm/migrations/2025-01-16-131336_transaction_ibc_types/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TYPE TRANSACTION_KIND ADD VALUE 'ibc_transparent_transfer'; +ALTER TYPE TRANSACTION_KIND ADD VALUE 'ibc_shielding_transfer'; +ALTER TYPE TRANSACTION_KIND ADD VALUE 'ibc_unshielding_transfer'; \ No newline at end of file diff --git a/orm/src/transactions.rs b/orm/src/transactions.rs index 599bab567..1e02cb6e5 100644 --- a/orm/src/transactions.rs +++ b/orm/src/transactions.rs @@ -18,6 +18,9 @@ pub enum TransactionKindDb { UnshieldingTransfer, MixedTransfer, IbcMsgTransfer, + IbcTransparentTransfer, + IbcShieldingTransfer, + IbcUnshieldingTransfer, Bond, Redelegation, Unbond, @@ -48,6 +51,15 @@ impl From for TransactionKindDb { TransactionKind::ShieldingTransfer(_) => Self::ShieldingTransfer, TransactionKind::MixedTransfer(_) => Self::MixedTransfer, TransactionKind::IbcMsgTransfer(_) => Self::IbcMsgTransfer, + TransactionKind::IbcTrasparentTransfer(_) => { + Self::IbcTransparentTransfer + } + TransactionKind::IbcShieldingTransfer(_) => { + Self::IbcShieldingTransfer + } + TransactionKind::IbcUnshieldingTransfer(_) => { + Self::IbcUnshieldingTransfer + } TransactionKind::Bond(_) => Self::Bond, TransactionKind::Redelegation(_) => Self::Redelegation, TransactionKind::Unbond(_) => Self::Unbond, diff --git a/shared/src/block.rs b/shared/src/block.rs index 30d78376c..4bacd70bb 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -389,6 +389,201 @@ impl Block { vec![] } } + TransactionKind::IbcTrasparentTransfer((ibc_message, _)) => { + if let Some(data) = ibc_message { + match data.0 { + IbcMessage::Transfer(transfer) => { + let sources = transfer + .clone() + .transfer + .unwrap_or_default() + .sources + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = transfer + .transfer + .unwrap_or_default() + .targets + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } + IbcMessage::NftTransfer(transfer) => { + let sources = transfer + .clone() + .transfer + .unwrap_or_default() + .sources + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = transfer + .transfer + .unwrap_or_default() + .targets + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } + _ => vec![], + } + } else { + vec![] + } + } + TransactionKind::IbcShieldingTransfer((ibc_message, _)) => { + if let Some(data) = ibc_message { + match data.0 { + IbcMessage::Transfer(transfer) => { + let sources = transfer + .clone() + .transfer + .unwrap_or_default() + .sources + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = transfer + .transfer + .unwrap_or_default() + .targets + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } + IbcMessage::NftTransfer(transfer) => { + let sources = transfer + .clone() + .transfer + .unwrap_or_default() + .sources + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = transfer + .transfer + .unwrap_or_default() + .targets + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } + _ => vec![], + } + } else { + vec![] + } + } + TransactionKind::IbcUnshieldingTransfer((ibc_message, _)) => { + if let Some(data) = ibc_message { + match data.0 { + IbcMessage::Transfer(transfer) => { + let sources = transfer + .clone() + .transfer + .unwrap_or_default() + .sources + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = transfer + .transfer + .unwrap_or_default() + .targets + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } + IbcMessage::NftTransfer(transfer) => { + let sources = transfer + .clone() + .transfer + .unwrap_or_default() + .sources + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + let targets = transfer + .transfer + .unwrap_or_default() + .targets + .keys() + .map(|account| { + TransactionTarget::sent( + tx.tx_id.clone(), + account.owner.to_string(), + ) + }) + .collect::>(); + [sources, targets].concat() + } + _ => vec![], + } + } else { + vec![] + } + } TransactionKind::Bond(bond) => { if let Some(data) = bond { let source = diff --git a/shared/src/transaction.rs b/shared/src/transaction.rs index c0554d311..6186c3068 100644 --- a/shared/src/transaction.rs +++ b/shared/src/transaction.rs @@ -38,6 +38,9 @@ pub enum TransactionKind { UnshieldingTransfer(Option), MixedTransfer(Option), IbcMsgTransfer(Option>), + IbcTrasparentTransfer((Option>, TransferData)), + IbcShieldingTransfer((Option>, TransferData)), + IbcUnshieldingTransfer((Option>, TransferData)), Bond(Option), Redelegation(Option), Unbond(Option), @@ -222,15 +225,152 @@ impl TransactionKind { TransactionKind::ReactivateValidator(data) } "tx_ibc" => { - let data = if let Ok(data) = + if let Ok(ibc_data) = namada_ibc::decode_message::(data) { - Some(data) + match ibc_data.clone() { + namada_ibc::IbcMessage::Envelope(_msg_envelope) => { + TransactionKind::IbcMsgTransfer(Some(IbcMessage( + ibc_data, + ))) + } + namada_ibc::IbcMessage::Transfer(transfer) => { + if let Some(data) = transfer.transfer { + let has_shielded_section = + data.shielded_section_hash.is_some(); + + let ( + all_sources_are_masp, + any_sources_are_masp, + ) = data.sources.iter().fold( + (true, false), + |(all, any), (acc, _)| { + let is_masp = + acc.owner.eq(masp_address); + (all && is_masp, any || is_masp) + }, + ); + + let ( + all_targets_are_masp, + any_targets_are_masp, + ) = data.targets.iter().fold( + (true, false), + |(all, any), (acc, _)| { + let is_masp = + acc.owner.eq(masp_address); + (all && is_masp, any || is_masp) + }, + ); + + match ( + all_sources_are_masp, + any_sources_are_masp, + all_targets_are_masp, + any_targets_are_masp, + has_shielded_section, + ) { + (true, _, _, false, true) => { + TransactionKind::IbcUnshieldingTransfer( + ( + Some(IbcMessage(ibc_data)), + data.into(), + ), + ) + } + (false, _, true, _, true) => { + TransactionKind::IbcShieldingTransfer(( + Some(IbcMessage(ibc_data)), + data.into(), + )) + } + (false, _, false, _, false) => { + TransactionKind::IbcTrasparentTransfer( + ( + Some(IbcMessage(ibc_data)), + data.into(), + ), + ) + } + _ => TransactionKind::MixedTransfer(Some( + data.into(), + )), + } + } else { + TransactionKind::IbcMsgTransfer(None) + } + } + namada_ibc::IbcMessage::NftTransfer(transfer) => { + if let Some(data) = transfer.transfer { + let has_shielded_section = + data.shielded_section_hash.is_some(); + + let ( + all_sources_are_masp, + any_sources_are_masp, + ) = data.sources.iter().fold( + (true, false), + |(all, any), (acc, _)| { + let is_masp = + acc.owner.eq(masp_address); + (all && is_masp, any || is_masp) + }, + ); + + let ( + all_targets_are_masp, + any_targets_are_masp, + ) = data.targets.iter().fold( + (true, false), + |(all, any), (acc, _)| { + let is_masp = + acc.owner.eq(masp_address); + (all && is_masp, any || is_masp) + }, + ); + + match ( + all_sources_are_masp, + any_sources_are_masp, + all_targets_are_masp, + any_targets_are_masp, + has_shielded_section, + ) { + (true, _, _, false, true) => { + TransactionKind::IbcUnshieldingTransfer( + ( + Some(IbcMessage(ibc_data)), + data.into(), + ), + ) + } + (false, _, true, _, true) => { + TransactionKind::IbcShieldingTransfer(( + Some(IbcMessage(ibc_data)), + data.into(), + )) + } + (false, _, false, _, false) => { + TransactionKind::IbcTrasparentTransfer( + ( + Some(IbcMessage(ibc_data)), + data.into(), + ), + ) + } + _ => TransactionKind::MixedTransfer(Some( + data.into(), + )), + } + } else { + TransactionKind::IbcMsgTransfer(None) + } + } + } } else { tracing::warn!("Cannot deserialize IBC transfer"); - None - }; - TransactionKind::IbcMsgTransfer(data.map(IbcMessage)) + TransactionKind::IbcMsgTransfer(None) + } } "tx_unjail_validator" => { let data = if let Ok(data) = Address::try_from_slice(data) { diff --git a/webserver/src/response/transaction.rs b/webserver/src/response/transaction.rs index eabff16ea..7baf1b767 100644 --- a/webserver/src/response/transaction.rs +++ b/webserver/src/response/transaction.rs @@ -30,6 +30,9 @@ pub enum TransactionKind { ChangeCommission, RevealPk, IbcMsgTransfer, + IbcTransparentTransfer, + IbcShieldingTransfer, + IbcUnshieldingTransfer, BecomeValidator, DeactivateValidator, ReactivateValidator, @@ -113,6 +116,15 @@ impl From for TransactionKind { TransactionKindDb::RevealPk => Self::RevealPk, TransactionKindDb::Unknown => Self::Unknown, TransactionKindDb::IbcMsgTransfer => Self::IbcMsgTransfer, + TransactionKindDb::IbcTransparentTransfer => { + Self::IbcTransparentTransfer + } + TransactionKindDb::IbcShieldingTransfer => { + Self::IbcShieldingTransfer + } + TransactionKindDb::IbcUnshieldingTransfer => { + Self::IbcUnshieldingTransfer + } TransactionKindDb::BecomeValidator => Self::BecomeValidator, TransactionKindDb::ReactivateValidator => Self::ReactivateValidator, TransactionKindDb::DeactivateValidator => Self::DeactivateValidator, From 6e5d483ee1463b54656714f09b6d1e496333881b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Wed, 15 Jan 2025 17:14:10 +0100 Subject: [PATCH 4/8] wip: added masp tables --- .../2025-01-15-135342_masp/down.sql | 8 + orm/migrations/2025-01-15-135342_masp/up.sql | 163 ++++++++++++++++++ orm/src/schema.rs | 104 ++++++----- 3 files changed, 220 insertions(+), 55 deletions(-) create mode 100644 orm/migrations/2025-01-15-135342_masp/down.sql create mode 100644 orm/migrations/2025-01-15-135342_masp/up.sql diff --git a/orm/migrations/2025-01-15-135342_masp/down.sql b/orm/migrations/2025-01-15-135342_masp/down.sql new file mode 100644 index 000000000..f3cd01588 --- /dev/null +++ b/orm/migrations/2025-01-15-135342_masp/down.sql @@ -0,0 +1,8 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS masp_pool; + +DROP TABLE IF EXISTS masp_pool_aggregate; + +DROP TYPE IF EXISTS MASP_POOL_AGGREGATE_WINDOW; + +DROP TYPE IF EXISTS MASP_POOL_AGGREGATE_KIND; \ No newline at end of file diff --git a/orm/migrations/2025-01-15-135342_masp/up.sql b/orm/migrations/2025-01-15-135342_masp/up.sql new file mode 100644 index 000000000..fc86cef5d --- /dev/null +++ b/orm/migrations/2025-01-15-135342_masp/up.sql @@ -0,0 +1,163 @@ +-- Your SQL goes here +CREATE TABLE masp_pool ( + id SERIAL PRIMARY KEY, + token_address VARCHAR(45) NOT NULL, + timestamp TIMESTAMP NOT NULL, + raw_amount NUMERIC(78, 0) NOT NULL, + inner_tx_id VARCHAR(64) NOT NULL, + CONSTRAINT fk_inner_tx_id FOREIGN KEY(inner_tx_id) REFERENCES inner_transactions(id) ON DELETE CASCADE +); + +CREATE INDEX index_masp_pool_address_timestamp ON masp_pool (token_address, timestamp DESC); + +CREATE TYPE MASP_POOL_AGGREGATE_WINDOW AS ENUM ( + '1', + '7', + '30', + 'Inf' +); + +CREATE TYPE MASP_POOL_AGGREGATE_KIND AS ENUM ( + 'inflows', + 'outflows' +); + +CREATE TABLE masp_pool_aggregate ( + id SERIAL PRIMARY KEY, + token_address VARCHAR(45) NOT NULL, + time_window MASP_POOL_AGGREGATE_WINDOW NOT NULL, + kind MASP_POOL_AGGREGATE_KIND NOT NULL, + total_amount NUMERIC(78, 0) NOT NULL DEFAULT 0 +); + +CREATE UNIQUE INDEX index_masp_pool_aggregate_token_address_window_kind ON masp_pool_aggregate (token_address, time_window, kind); + +-- if it doesn't work ask for a fix to https://chatgpt.com +CREATE OR REPLACE FUNCTION update_masp_pool_aggregate_sum() +RETURNS TRIGGER AS $$ +-- +-- This function is triggered before an insert into the `masp_pool` table. +-- It calculates the running sum of amounts for different time windows (1-day, 7-day, 30-day, and all-time). +-- Depending on whether the `raw_amount` is positive or negative, it updates the corresponding `inflow` or `outflow` +-- entry in the `masp_pool_aggregate` table. +-- +-- The `inflow` entry is updated if `raw_amount` is positive, while the `outflow` entry is updated if `raw_amount` +-- is negative. The sum is incrementally updated for each of the windows: +-- 1-day, 7-day, 30-day, and all-time. +-- +-- The trigger ensures that the `masp_pool_aggregate` table reflects the running total of both inflow and outflow +-- amounts, for each token address, over different time windows. +-- +DECLARE + cutoff_1d TIMESTAMP := now() - INTERVAL '1 day'; + cutoff_7d TIMESTAMP := now() - INTERVAL '7 days'; + cutoff_30d TIMESTAMP := now() - INTERVAL '30 days'; +BEGIN + -- Update 1-day time_window for 'inflow' or 'outflow' + IF NEW.raw_amount > 0 THEN + -- Inflow: update inflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + '1d', + 'inflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + ELSE + -- Outflow: update outflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + '1d', + 'outflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + END IF; + + -- Update 7-day time_window for 'inflow' or 'outflow' + IF NEW.raw_amount > 0 THEN + -- Inflow: update inflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + '7d', + 'inflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + ELSE + -- Outflow: update outflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + '7d', + 'outflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + END IF; + + -- Update 30-day time_window for 'inflow' or 'outflow' + IF NEW.raw_amount > 0 THEN + -- Inflow: update inflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + '30d', + 'inflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + ELSE + -- Outflow: update outflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + '30d', + 'outflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + END IF; + + -- Update all-time time_window for 'inflow' or 'outflow' + IF NEW.raw_amount > 0 THEN + -- Inflow: update inflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + 'Inf', + 'inflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + ELSE + -- Outflow: update outflow entry + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + 'Inf', + 'outflows', + NEW.raw_amount + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_masp_pool_aggregate_sum_trigger +AFTER INSERT ON masp_pool +FOR EACH ROW +EXECUTE FUNCTION update_masp_pool_aggregate_sum(); \ No newline at end of file diff --git a/orm/src/schema.rs b/orm/src/schema.rs index 4904ddbd3..7c616487e 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -1,91 +1,55 @@ // @generated automatically by Diesel CLI. pub mod sql_types { - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "crawler_name"))] pub struct CrawlerName; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "governance_kind"))] pub struct GovernanceKind; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "governance_result"))] pub struct GovernanceResult; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "governance_tally_type"))] pub struct GovernanceTallyType; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "history_kind"))] pub struct HistoryKind; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "ibc_status"))] pub struct IbcStatus; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "masp_pool_aggregate_kind"))] + pub struct MaspPoolAggregateKind; + + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "masp_pool_aggregate_window"))] + pub struct MaspPoolAggregateWindow; + + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "token_type"))] pub struct TokenType; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "transaction_kind"))] pub struct TransactionKind; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "transaction_result"))] pub struct TransactionResult; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "validator_state"))] pub struct ValidatorState; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "vote_kind"))] pub struct VoteKind; } @@ -268,6 +232,33 @@ diesel::table! { } } +diesel::table! { + masp_pool (id) { + id -> Int4, + #[max_length = 45] + token_address -> Varchar, + timestamp -> Timestamp, + raw_amount -> Numeric, + #[max_length = 64] + inner_tx_id -> Varchar, + } +} + +diesel::table! { + use diesel::sql_types::*; + use super::sql_types::MaspPoolAggregateWindow; + use super::sql_types::MaspPoolAggregateKind; + + masp_pool_aggregate (id) { + id -> Int4, + #[max_length = 45] + token_address -> Varchar, + time_window -> MaspPoolAggregateWindow, + kind -> MaspPoolAggregateKind, + total_amount -> Numeric, + } +} + diesel::table! { pos_rewards (id) { id -> Int4, @@ -363,6 +354,7 @@ diesel::joinable!(gas_estimations -> wrapper_transactions (wrapper_id)); diesel::joinable!(governance_votes -> governance_proposals (proposal_id)); diesel::joinable!(ibc_token -> token (address)); diesel::joinable!(inner_transactions -> wrapper_transactions (wrapper_id)); +diesel::joinable!(masp_pool -> inner_transactions (inner_tx_id)); diesel::joinable!(pos_rewards -> validators (validator_id)); diesel::joinable!(transaction_history -> inner_transactions (inner_tx_id)); diesel::joinable!(unbonds -> validators (validator_id)); @@ -382,6 +374,8 @@ diesel::allow_tables_to_appear_in_same_query!( ibc_ack, ibc_token, inner_transactions, + masp_pool, + masp_pool_aggregate, pos_rewards, revealed_pk, token, From 9ef40dda3fb8b8cf022ab6b26963be8676179fa7 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 16 Jan 2025 06:22:38 +0100 Subject: [PATCH 5/8] added orm entities --- orm/src/lib.rs | 1 + orm/src/masp.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 orm/src/masp.rs diff --git a/orm/src/lib.rs b/orm/src/lib.rs index 4cce8ef60..501982ecb 100644 --- a/orm/src/lib.rs +++ b/orm/src/lib.rs @@ -18,3 +18,4 @@ pub mod transactions; pub mod unbond; pub mod validators; pub mod views; +pub mod masp; diff --git a/orm/src/masp.rs b/orm/src/masp.rs new file mode 100644 index 000000000..a01e712c7 --- /dev/null +++ b/orm/src/masp.rs @@ -0,0 +1,44 @@ +use bigdecimal::BigDecimal; +use diesel::Insertable; + +use crate::schema::{masp_pool, masp_pool_aggregate}; + +#[derive(Insertable, Clone, Debug)] +#[diesel(table_name = masp_pool)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct MaspDb { + pub id: i32, + pub token_address: String, + pub timestamp: chrono::NaiveDateTime, + pub raw_amount: BigDecimal, + pub inner_tx_id: String, +} + +pub type MaspInsertDb = MaspDb; + +#[derive(Debug, Clone, diesel_derive_enum::DbEnum)] +#[ExistingTypePath = "crate::schema::sql_types::MaspPoolAggregateWindow"] +pub enum MaspPoolAggregateWindow { + OneDay, + SevenDay, + OneMonth, + Inf, +} + +#[derive(Debug, Clone, diesel_derive_enum::DbEnum)] +#[ExistingTypePath = "crate::schema::sql_types::MaspPoolAggregateKind"] +pub enum MaspPoolAggregateKind { + Inflows, + Outflows, +} + +#[derive(Insertable, Clone, Debug)] +#[diesel(table_name = masp_pool_aggregate)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct MaspPoolDb { + pub id: i32, + pub token_address: String, + pub time_window: MaspPoolAggregateWindow, + pub kind: MaspPoolAggregateKind, + pub total_amount: BigDecimal, +} From ce3aef100192230089cc8e879b3234b636350481 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 16 Jan 2025 10:55:56 +0100 Subject: [PATCH 6/8] wip --- shared/src/block.rs | 49 +++++++++++++++++++++++++++++++++++++++++++- shared/src/header.rs | 2 +- shared/src/lib.rs | 1 + shared/src/masp.rs | 9 ++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 shared/src/masp.rs diff --git a/shared/src/block.rs b/shared/src/block.rs index 4bacd70bb..7fa696ce6 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -16,6 +16,7 @@ use crate::bond::BondAddresses; use crate::checksums::Checksums; use crate::header::BlockHeader; use crate::id::Id; +use crate::masp::MaspEntry; use crate::proposal::{GovernanceProposal, GovernanceProposalKind}; use crate::public_key::PublicKey; use crate::token::{IbcToken, Token}; @@ -153,7 +154,7 @@ impl Block { proposer_address_namada: proposer_address_namada .as_ref() .map(Id::to_string), - timestamp: block_response.block.header.time.to_string(), + timestamp: block_response.block.header.time.unix_timestamp(), app_hash: Id::from(&block_response.block.header.app_hash), }, transactions, @@ -738,6 +739,52 @@ impl Block { .collect::>() } + pub fn masp(&self) -> Vec { + self + .transactions + .iter() + .flat_map(|(_, txs)| txs) + .filter(|tx| tx.data.is_some() && tx.was_successful()) + .map(|tx| match &tx.kind { + TransactionKind::ShieldingTransfer(Some(transfer_data)) => { + transfer_data.targets.0.iter().map(|(account, amount)| MaspEntry { + token_address: account.token.to_string(), + timestamp: self.header.timestamp, + raw_amount: amount.amount().into(), + inner_tx_id: tx.tx_id, + }).collect() + }, + TransactionKind::UnshieldingTransfer(Some(transfer_data)) => { + transfer_data.sources.0.iter().map(|(account, amount)| MaspEntry { + token_address: account.token.to_string(), + timestamp: self.header.timestamp, + raw_amount: amount.amount().into(), + inner_tx_id: tx.tx_id, + }).collect() + }, + TransactionKind::IbcMsgTransfer(Some(ibc_message)) => { + match ibc_message.0 { + IbcMessage::Transfer(transfer) => { + if let Some(transfer_data) = transfer.transfer { + + } else { + vec![] + } + }, + IbcMessage::NftTransfer(transfer) => { + if let Some(transfer_data) = transfer.transfer { + + } else { + vec![] + } + }, + _ => vec![] + } + }, + _ => vec![] + }).flatten() + } + pub fn governance_proposal( &self, mut next_proposal_id: u64, diff --git a/shared/src/header.rs b/shared/src/header.rs index decf3a9f9..520377818 100644 --- a/shared/src/header.rs +++ b/shared/src/header.rs @@ -6,6 +6,6 @@ pub struct BlockHeader { pub height: BlockHeight, pub proposer_address_tm: String, pub proposer_address_namada: Option, - pub timestamp: String, + pub timestamp: i64, pub app_hash: Id, } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 9d0f5ad46..e48d3ea7e 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -23,3 +23,4 @@ pub mod unbond; pub mod utils; pub mod validator; pub mod vote; +pub mod masp; diff --git a/shared/src/masp.rs b/shared/src/masp.rs new file mode 100644 index 000000000..72b7d83d1 --- /dev/null +++ b/shared/src/masp.rs @@ -0,0 +1,9 @@ +use crate::{balance::Amount, id::Id}; + +#[derive(Debug, Clone)] +pub struct MaspEntry { + pub token_address: String, + pub timestamp: i64, + pub raw_amount: Amount, + pub inner_tx_id: Id, +} \ No newline at end of file From 59a64f3a65f44057a0c8f7c786465078a336eb4e Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 17 Jan 2025 08:47:15 +0100 Subject: [PATCH 7/8] more --- orm/migrations/2025-01-15-135342_masp/up.sql | 15 ++- orm/src/lib.rs | 2 +- orm/src/masp.rs | 34 ++++++- orm/src/schema.rs | 8 ++ shared/src/block.rs | 101 +++++++++++-------- shared/src/lib.rs | 2 +- shared/src/masp.rs | 12 ++- transactions/src/main.rs | 6 +- transactions/src/repository/masp.rs | 22 ++++ transactions/src/repository/mod.rs | 1 + 10 files changed, 152 insertions(+), 51 deletions(-) create mode 100644 transactions/src/repository/masp.rs diff --git a/orm/migrations/2025-01-15-135342_masp/up.sql b/orm/migrations/2025-01-15-135342_masp/up.sql index fc86cef5d..3a4930cd6 100644 --- a/orm/migrations/2025-01-15-135342_masp/up.sql +++ b/orm/migrations/2025-01-15-135342_masp/up.sql @@ -1,14 +1,21 @@ -- Your SQL goes here +CREATE TYPE MASP_POOL_DIRECTION AS ENUM ( + 'in', + 'out' +); + CREATE TABLE masp_pool ( id SERIAL PRIMARY KEY, token_address VARCHAR(45) NOT NULL, timestamp TIMESTAMP NOT NULL, raw_amount NUMERIC(78, 0) NOT NULL, + direction MASP_POOL_DIRECTION NOT NULL, inner_tx_id VARCHAR(64) NOT NULL, CONSTRAINT fk_inner_tx_id FOREIGN KEY(inner_tx_id) REFERENCES inner_transactions(id) ON DELETE CASCADE ); CREATE INDEX index_masp_pool_address_timestamp ON masp_pool (token_address, timestamp DESC); +CREATE UNIQUE INDEX index_masp_pool_inner_tx_id ON masp_pool (inner_tx_id); CREATE TYPE MASP_POOL_AGGREGATE_WINDOW AS ENUM ( '1', @@ -54,7 +61,7 @@ DECLARE cutoff_30d TIMESTAMP := now() - INTERVAL '30 days'; BEGIN -- Update 1-day time_window for 'inflow' or 'outflow' - IF NEW.raw_amount > 0 THEN + IF NEW.direction = 'in' THEN -- Inflow: update inflow entry INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) VALUES ( @@ -79,7 +86,7 @@ BEGIN END IF; -- Update 7-day time_window for 'inflow' or 'outflow' - IF NEW.raw_amount > 0 THEN + IF NEW.direction = 'in' THEN -- Inflow: update inflow entry INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) VALUES ( @@ -104,7 +111,7 @@ BEGIN END IF; -- Update 30-day time_window for 'inflow' or 'outflow' - IF NEW.raw_amount > 0 THEN + IF NEW.direction = 'in' THEN -- Inflow: update inflow entry INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) VALUES ( @@ -129,7 +136,7 @@ BEGIN END IF; -- Update all-time time_window for 'inflow' or 'outflow' - IF NEW.raw_amount > 0 THEN + IF NEW.direction = 'in' THEN -- Inflow: update inflow entry INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) VALUES ( diff --git a/orm/src/lib.rs b/orm/src/lib.rs index 501982ecb..32698e31f 100644 --- a/orm/src/lib.rs +++ b/orm/src/lib.rs @@ -8,6 +8,7 @@ pub mod governance_votes; pub mod group_by_macros; pub mod helpers; pub mod ibc; +pub mod masp; pub mod migrations; pub mod parameters; pub mod pos_rewards; @@ -18,4 +19,3 @@ pub mod transactions; pub mod unbond; pub mod validators; pub mod views; -pub mod masp; diff --git a/orm/src/masp.rs b/orm/src/masp.rs index a01e712c7..abbfaffde 100644 --- a/orm/src/masp.rs +++ b/orm/src/masp.rs @@ -1,16 +1,26 @@ +use std::str::FromStr; + use bigdecimal::BigDecimal; use diesel::Insertable; +use shared::masp::{MaspEntry, MaspEntryDirection}; use crate::schema::{masp_pool, masp_pool_aggregate}; +#[derive(Debug, Clone, diesel_derive_enum::DbEnum)] +#[ExistingTypePath = "crate::schema::sql_types::MaspPoolDirection"] +pub enum MaspPoolDirectionDb { + In, + Out, +} + #[derive(Insertable, Clone, Debug)] #[diesel(table_name = masp_pool)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct MaspDb { - pub id: i32, pub token_address: String, pub timestamp: chrono::NaiveDateTime, pub raw_amount: BigDecimal, + pub direction: MaspPoolDirectionDb, pub inner_tx_id: String, } @@ -42,3 +52,25 @@ pub struct MaspPoolDb { pub kind: MaspPoolAggregateKind, pub total_amount: BigDecimal, } + +impl From for MaspInsertDb { + fn from(value: MaspEntry) -> Self { + let timestamp = chrono::DateTime::from_timestamp(value.timestamp, 0) + .expect("Invalid timestamp") + .naive_utc(); + + let amount = BigDecimal::from_str(&value.raw_amount.to_string()) + .expect("Invalid amount"); + + MaspInsertDb { + token_address: value.token_address, + timestamp, + raw_amount: amount, + direction: match value.direction { + MaspEntryDirection::In => MaspPoolDirectionDb::In, + MaspEntryDirection::Out => MaspPoolDirectionDb::Out, + }, + inner_tx_id: value.inner_tx_id.to_string(), + } + } +} diff --git a/orm/src/schema.rs b/orm/src/schema.rs index 7c616487e..23528df06 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -33,6 +33,10 @@ pub mod sql_types { #[diesel(postgres_type(name = "masp_pool_aggregate_window"))] pub struct MaspPoolAggregateWindow; + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "masp_pool_direction"))] + pub struct MaspPoolDirection; + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "token_type"))] pub struct TokenType; @@ -233,12 +237,16 @@ diesel::table! { } diesel::table! { + use diesel::sql_types::*; + use super::sql_types::MaspPoolDirection; + masp_pool (id) { id -> Int4, #[max_length = 45] token_address -> Varchar, timestamp -> Timestamp, raw_amount -> Numeric, + direction -> MaspPoolDirection, #[max_length = 64] inner_tx_id -> Varchar, } diff --git a/shared/src/block.rs b/shared/src/block.rs index 7fa696ce6..d0ee3252f 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -16,7 +16,7 @@ use crate::bond::BondAddresses; use crate::checksums::Checksums; use crate::header::BlockHeader; use crate::id::Id; -use crate::masp::MaspEntry; +use crate::masp::{MaspEntry, MaspEntryDirection}; use crate::proposal::{GovernanceProposal, GovernanceProposalKind}; use crate::public_key::PublicKey; use crate::token::{IbcToken, Token}; @@ -739,50 +739,71 @@ impl Block { .collect::>() } - pub fn masp(&self) -> Vec { - self - .transactions + pub fn masp_entries(&self) -> Vec { + self.transactions .iter() .flat_map(|(_, txs)| txs) .filter(|tx| tx.data.is_some() && tx.was_successful()) - .map(|tx| match &tx.kind { + .flat_map(|tx| match &tx.kind { TransactionKind::ShieldingTransfer(Some(transfer_data)) => { - transfer_data.targets.0.iter().map(|(account, amount)| MaspEntry { - token_address: account.token.to_string(), - timestamp: self.header.timestamp, - raw_amount: amount.amount().into(), - inner_tx_id: tx.tx_id, - }).collect() - }, + transfer_data + .targets + .0 + .iter() + .map(|(account, amount)| MaspEntry { + token_address: account.token.to_string(), + timestamp: self.header.timestamp, + raw_amount: amount.amount().into(), + direction: MaspEntryDirection::In, + inner_tx_id: tx.tx_id.clone(), + }) + .collect() + } TransactionKind::UnshieldingTransfer(Some(transfer_data)) => { - transfer_data.sources.0.iter().map(|(account, amount)| MaspEntry { - token_address: account.token.to_string(), - timestamp: self.header.timestamp, - raw_amount: amount.amount().into(), - inner_tx_id: tx.tx_id, - }).collect() - }, - TransactionKind::IbcMsgTransfer(Some(ibc_message)) => { - match ibc_message.0 { - IbcMessage::Transfer(transfer) => { - if let Some(transfer_data) = transfer.transfer { - - } else { - vec![] - } - }, - IbcMessage::NftTransfer(transfer) => { - if let Some(transfer_data) = transfer.transfer { - - } else { - vec![] - } - }, - _ => vec![] - } - }, - _ => vec![] - }).flatten() + transfer_data + .sources + .0 + .iter() + .map(|(account, amount)| MaspEntry { + token_address: account.token.to_string(), + timestamp: self.header.timestamp, + raw_amount: amount.amount().into(), + direction: MaspEntryDirection::Out, + inner_tx_id: tx.tx_id.clone(), + }) + .collect() + } + TransactionKind::IbcShieldingTransfer((_, transfer_data)) => { + transfer_data + .targets + .0 + .iter() + .map(|(account, amount)| MaspEntry { + token_address: account.token.to_string(), + timestamp: self.header.timestamp, + raw_amount: amount.amount().into(), + direction: MaspEntryDirection::In, + inner_tx_id: tx.tx_id.clone(), + }) + .collect() + } + TransactionKind::IbcUnshieldingTransfer((_, transfer_data)) => { + transfer_data + .sources + .0 + .iter() + .map(|(account, amount)| MaspEntry { + token_address: account.token.to_string(), + timestamp: self.header.timestamp, + raw_amount: amount.amount().into(), + direction: MaspEntryDirection::Out, + inner_tx_id: tx.tx_id.clone(), + }) + .collect() + } // we could improve this by looking at mixed transfers too + _ => vec![], + }) + .collect() } pub fn governance_proposal( diff --git a/shared/src/lib.rs b/shared/src/lib.rs index e48d3ea7e..f36108245 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -11,6 +11,7 @@ pub mod genesis; pub mod header; pub mod id; pub mod log_config; +pub mod masp; pub mod parameters; pub mod proposal; pub mod public_key; @@ -23,4 +24,3 @@ pub mod unbond; pub mod utils; pub mod validator; pub mod vote; -pub mod masp; diff --git a/shared/src/masp.rs b/shared/src/masp.rs index 72b7d83d1..93824470e 100644 --- a/shared/src/masp.rs +++ b/shared/src/masp.rs @@ -1,9 +1,17 @@ -use crate::{balance::Amount, id::Id}; +use crate::balance::Amount; +use crate::id::Id; + +#[derive(Debug, Clone)] +pub enum MaspEntryDirection { + In, + Out, +} #[derive(Debug, Clone)] pub struct MaspEntry { pub token_address: String, pub timestamp: i64, pub raw_amount: Amount, + pub direction: MaspEntryDirection, pub inner_tx_id: Id, -} \ No newline at end of file +} diff --git a/transactions/src/main.rs b/transactions/src/main.rs index adb125816..80baf41e2 100644 --- a/transactions/src/main.rs +++ b/transactions/src/main.rs @@ -143,6 +143,7 @@ async fn crawling_fn( let inner_txs = block.inner_txs(); let wrapper_txs = block.wrapper_txs(); let transaction_sources = block.sources(); + let masp_entries = block.masp_entries(); let gas_estimates = tx_service::get_gas_estimates(&inner_txs, &wrapper_txs); let ibc_sequence_packet = @@ -150,10 +151,11 @@ async fn crawling_fn( let ibc_ack_packet = tx_service::get_ibc_ack_packet(&inner_txs); tracing::info!( - "Deserialized {} wrappers, {} inners, {} ibc sequence numbers and {} \ - ibc acks events...", + "Deserialized {} wrappers, {} inners, {} masp entries, {} ibc \ + sequence numbers and {} ibc acks events...", wrapper_txs.len(), inner_txs.len(), + masp_entries.len(), ibc_sequence_packet.len(), ibc_ack_packet.len() ); diff --git a/transactions/src/repository/masp.rs b/transactions/src/repository/masp.rs new file mode 100644 index 000000000..bf94eeb46 --- /dev/null +++ b/transactions/src/repository/masp.rs @@ -0,0 +1,22 @@ +use anyhow::Context; +use diesel::{PgConnection, RunQueryDsl}; +use orm::masp::MaspInsertDb; +use orm::schema::masp_pool; +use shared::masp::MaspEntry; + +pub fn insert_masp_entries( + transaction_conn: &mut PgConnection, + masp_entries: Vec, +) -> anyhow::Result<()> { + diesel::insert_into(masp_pool::table) + .values::<&Vec>( + &masp_entries + .into_iter() + .map(MaspInsertDb::from) + .collect::>(), + ) + .execute(transaction_conn) + .context("Failed to insert masp pool entries in db")?; + + anyhow::Ok(()) +} diff --git a/transactions/src/repository/mod.rs b/transactions/src/repository/mod.rs index 5ae69f54d..228bf7fbd 100644 --- a/transactions/src/repository/mod.rs +++ b/transactions/src/repository/mod.rs @@ -1,2 +1,3 @@ pub mod block; +pub mod masp; pub mod transactions; From a8838a73977293cbd582c812d6d2a9227ff80429 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 17 Jan 2025 12:54:48 +0100 Subject: [PATCH 8/8] aggregate masp data --- orm/migrations/2025-01-15-135342_masp/up.sql | 197 ++++++++----------- orm/src/masp.rs | 18 +- orm/src/schema.rs | 84 ++++++-- shared/src/block.rs | 59 ++---- transactions/src/main.rs | 4 +- transactions/src/repository/masp.rs | 1 + webserver/src/app.rs | 6 +- webserver/src/dto/masp.rs | 8 + webserver/src/dto/mod.rs | 1 + webserver/src/error/api.rs | 4 + webserver/src/error/masp.rs | 24 +++ webserver/src/error/mod.rs | 1 + webserver/src/handler/masp.rs | 24 +++ webserver/src/handler/mod.rs | 1 + webserver/src/repository/masp.rs | 60 ++++++ webserver/src/repository/mod.rs | 1 + webserver/src/response/masp.rs | 60 ++++++ webserver/src/response/mod.rs | 1 + webserver/src/service/masp.rs | 43 ++++ webserver/src/service/mod.rs | 1 + webserver/src/state/common.rs | 3 + 21 files changed, 416 insertions(+), 185 deletions(-) create mode 100644 webserver/src/dto/masp.rs create mode 100644 webserver/src/error/masp.rs create mode 100644 webserver/src/handler/masp.rs create mode 100644 webserver/src/repository/masp.rs create mode 100644 webserver/src/response/masp.rs create mode 100644 webserver/src/service/masp.rs diff --git a/orm/migrations/2025-01-15-135342_masp/up.sql b/orm/migrations/2025-01-15-135342_masp/up.sql index 3a4930cd6..e6b24a0dd 100644 --- a/orm/migrations/2025-01-15-135342_masp/up.sql +++ b/orm/migrations/2025-01-15-135342_masp/up.sql @@ -18,10 +18,10 @@ CREATE INDEX index_masp_pool_address_timestamp ON masp_pool (token_address, time CREATE UNIQUE INDEX index_masp_pool_inner_tx_id ON masp_pool (inner_tx_id); CREATE TYPE MASP_POOL_AGGREGATE_WINDOW AS ENUM ( - '1', - '7', - '30', - 'Inf' + 'one_day', + 'seven_days', + 'thirty_days', + 'all_time' ); CREATE TYPE MASP_POOL_AGGREGATE_KIND AS ENUM ( @@ -39,126 +39,95 @@ CREATE TABLE masp_pool_aggregate ( CREATE UNIQUE INDEX index_masp_pool_aggregate_token_address_window_kind ON masp_pool_aggregate (token_address, time_window, kind); --- if it doesn't work ask for a fix to https://chatgpt.com + CREATE OR REPLACE FUNCTION update_masp_pool_aggregate_sum() RETURNS TRIGGER AS $$ --- --- This function is triggered before an insert into the `masp_pool` table. --- It calculates the running sum of amounts for different time windows (1-day, 7-day, 30-day, and all-time). --- Depending on whether the `raw_amount` is positive or negative, it updates the corresponding `inflow` or `outflow` --- entry in the `masp_pool_aggregate` table. --- --- The `inflow` entry is updated if `raw_amount` is positive, while the `outflow` entry is updated if `raw_amount` --- is negative. The sum is incrementally updated for each of the windows: --- 1-day, 7-day, 30-day, and all-time. --- --- The trigger ensures that the `masp_pool_aggregate` table reflects the running total of both inflow and outflow --- amounts, for each token address, over different time windows. --- DECLARE cutoff_1d TIMESTAMP := now() - INTERVAL '1 day'; cutoff_7d TIMESTAMP := now() - INTERVAL '7 days'; cutoff_30d TIMESTAMP := now() - INTERVAL '30 days'; + nk MASP_POOL_AGGREGATE_KIND; -- Declare kind as the ENUM type BEGIN - -- Update 1-day time_window for 'inflow' or 'outflow' - IF NEW.direction = 'in' THEN - -- Inflow: update inflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - '1d', - 'inflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - ELSE - -- Outflow: update outflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - '1d', - 'outflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - END IF; + -- Determine the kind based on the direction + nk := CASE + WHEN NEW.direction = 'in' THEN 'inflows'::MASP_POOL_AGGREGATE_KIND + ELSE 'outflows'::MASP_POOL_AGGREGATE_KIND + END; + -- 1 day + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + 'one_day', + nk, + (SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction + AND timestamp >= cutoff_1d) + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = ( + SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction + AND timestamp >= cutoff_1d + ); - -- Update 7-day time_window for 'inflow' or 'outflow' - IF NEW.direction = 'in' THEN - -- Inflow: update inflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - '7d', - 'inflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - ELSE - -- Outflow: update outflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - '7d', - 'outflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - END IF; + -- 7 days + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + 'seven_days', + nk, + (SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction + AND timestamp >= cutoff_1d) + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = ( + SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction + AND timestamp >= cutoff_7d + ); - -- Update 30-day time_window for 'inflow' or 'outflow' - IF NEW.direction = 'in' THEN - -- Inflow: update inflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - '30d', - 'inflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - ELSE - -- Outflow: update outflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - '30d', - 'outflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - END IF; + -- 30 days + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + 'thirty_days', + nk, + (SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction + AND timestamp >= cutoff_1d) + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = ( + SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction + AND timestamp >= cutoff_30d + ); - -- Update all-time time_window for 'inflow' or 'outflow' - IF NEW.direction = 'in' THEN - -- Inflow: update inflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - 'Inf', - 'inflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - ELSE - -- Outflow: update outflow entry - INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) - VALUES ( - NEW.token_address, - 'Inf', - 'outflows', - NEW.raw_amount - ) - ON CONFLICT (token_address, time_window, kind) - DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + EXCLUDED.total_amount; - END IF; + INSERT INTO masp_pool_aggregate (token_address, time_window, kind, total_amount) + VALUES ( + NEW.token_address, + 'all_time', + nk, + (SELECT COALESCE(SUM(raw_amount), 0) + FROM masp_pool + WHERE token_address = NEW.token_address + AND direction = NEW.direction) + ) + ON CONFLICT (token_address, time_window, kind) + DO UPDATE SET total_amount = masp_pool_aggregate.total_amount + NEW.raw_amount; RETURN NEW; END; diff --git a/orm/src/masp.rs b/orm/src/masp.rs index abbfaffde..c81317688 100644 --- a/orm/src/masp.rs +++ b/orm/src/masp.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use bigdecimal::BigDecimal; -use diesel::Insertable; +use diesel::{Insertable, Queryable, Selectable}; use shared::masp::{MaspEntry, MaspEntryDirection}; use crate::schema::{masp_pool, masp_pool_aggregate}; @@ -28,28 +28,28 @@ pub type MaspInsertDb = MaspDb; #[derive(Debug, Clone, diesel_derive_enum::DbEnum)] #[ExistingTypePath = "crate::schema::sql_types::MaspPoolAggregateWindow"] -pub enum MaspPoolAggregateWindow { +pub enum MaspPoolAggregateWindowDb { OneDay, - SevenDay, - OneMonth, - Inf, + SevenDays, + ThirtyDays, + AllTime, } #[derive(Debug, Clone, diesel_derive_enum::DbEnum)] #[ExistingTypePath = "crate::schema::sql_types::MaspPoolAggregateKind"] -pub enum MaspPoolAggregateKind { +pub enum MaspPoolAggregateKindDb { Inflows, Outflows, } -#[derive(Insertable, Clone, Debug)] +#[derive(Insertable, Queryable, Selectable, Clone, Debug)] #[diesel(table_name = masp_pool_aggregate)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct MaspPoolDb { pub id: i32, pub token_address: String, - pub time_window: MaspPoolAggregateWindow, - pub kind: MaspPoolAggregateKind, + pub time_window: MaspPoolAggregateWindowDb, + pub kind: MaspPoolAggregateKindDb, pub total_amount: BigDecimal, } diff --git a/orm/src/schema.rs b/orm/src/schema.rs index 23528df06..3f3aad024 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -1,59 +1,115 @@ // @generated automatically by Diesel CLI. pub mod sql_types { - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "crawler_name"))] pub struct CrawlerName; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "governance_kind"))] pub struct GovernanceKind; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "governance_result"))] pub struct GovernanceResult; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "governance_tally_type"))] pub struct GovernanceTallyType; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "history_kind"))] pub struct HistoryKind; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "ibc_status"))] pub struct IbcStatus; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "masp_pool_aggregate_kind"))] pub struct MaspPoolAggregateKind; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "masp_pool_aggregate_window"))] pub struct MaspPoolAggregateWindow; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "masp_pool_direction"))] pub struct MaspPoolDirection; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "token_type"))] pub struct TokenType; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "transaction_kind"))] pub struct TransactionKind; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "transaction_result"))] pub struct TransactionResult; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "validator_state"))] pub struct ValidatorState; - #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] #[diesel(postgres_type(name = "vote_kind"))] pub struct VoteKind; } diff --git a/shared/src/block.rs b/shared/src/block.rs index d0ee3252f..dc786227c 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -21,8 +21,8 @@ use crate::proposal::{GovernanceProposal, GovernanceProposalKind}; use crate::public_key::PublicKey; use crate::token::{IbcToken, Token}; use crate::transaction::{ - InnerTransaction, Transaction, TransactionExitStatus, TransactionKind, - TransactionTarget, WrapperTransaction, + InnerTransaction, Transaction, TransactionKind, TransactionTarget, + WrapperTransaction, }; use crate::unbond::UnbondAddresses; use crate::utils::BalanceChange; @@ -813,10 +813,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::InitProposal(data) => { let init_proposal_data = data.clone().unwrap(); // safe as we filter before (not the best pattern) @@ -893,10 +890,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::ClaimRewards(data) => { let data = data.clone().unwrap(); @@ -914,10 +908,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::ProposalVote(data) => { let vote_proposal_data = data.clone().unwrap(); @@ -937,10 +928,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::IbcMsgTransfer(data) => { let data = data.clone().and_then(|d| { @@ -1148,10 +1136,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::BecomeValidator(data) => { let data = data.clone().unwrap(); @@ -1180,10 +1165,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::DeactivateValidator(data) => { let data = data.clone().unwrap(); @@ -1208,10 +1190,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::Bond(data) => { let bond_data = data.clone().unwrap(); @@ -1264,10 +1243,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::Unbond(data) => { let unbond_data = data.clone().unwrap(); @@ -1291,10 +1267,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::Withdraw(data) => { let withdraw_data = data.clone().unwrap(); @@ -1318,10 +1291,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::MetadataChange(data) => { let metadata_change_data = data.clone().unwrap(); @@ -1368,10 +1338,7 @@ impl Block { self.transactions .iter() .flat_map(|(_, txs)| txs) - .filter(|tx| { - tx.data.is_some() - && tx.exit_code == TransactionExitStatus::Applied - }) + .filter(|tx| tx.data.is_some() && tx.was_successful()) .filter_map(|tx| match &tx.kind { TransactionKind::RevealPk(data) => { let namada_public_key = data.clone().unwrap().public_key; diff --git a/transactions/src/main.rs b/transactions/src/main.rs index 80baf41e2..3053ace1b 100644 --- a/transactions/src/main.rs +++ b/transactions/src/main.rs @@ -17,7 +17,7 @@ use tendermint_rpc::HttpClient; use transactions::app_state::AppState; use transactions::config::AppConfig; use transactions::repository::{ - block as block_repo, transactions as transaction_repo, + block as block_repo, masp as masp_repo, transactions as transaction_repo, }; use transactions::services::{ db as db_service, namada as namada_service, @@ -217,6 +217,8 @@ async fn crawling_fn( gas_estimates, )?; + masp_repo::insert_masp_entries(transaction_conn, masp_entries)?; + anyhow::Ok(()) }) }) diff --git a/transactions/src/repository/masp.rs b/transactions/src/repository/masp.rs index bf94eeb46..fb27fc010 100644 --- a/transactions/src/repository/masp.rs +++ b/transactions/src/repository/masp.rs @@ -15,6 +15,7 @@ pub fn insert_masp_entries( .map(MaspInsertDb::from) .collect::>(), ) + .on_conflict_do_nothing() .execute(transaction_conn) .context("Failed to insert masp pool entries in db")?; diff --git a/webserver/src/app.rs b/webserver/src/app.rs index 7151795d8..0dca370f6 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -22,7 +22,7 @@ use crate::handler::{ balance as balance_handlers, block as block_handlers, chain as chain_handlers, crawler_state as crawler_state_handlers, gas as gas_handlers, governance as gov_handlers, ibc as ibc_handler, - pk as pk_handlers, pos as pos_handlers, + masp as masp_handlers, pk as pk_handlers, pos as pos_handlers, transaction as transaction_handlers, }; use crate::state::common::CommonState; @@ -150,6 +150,10 @@ impl ApplicationServer { "/block/timestamp/:value", get(block_handlers::get_block_by_timestamp), ) + .route( + "/masp/aggregates", + get(masp_handlers::get_masp_aggregates), + ) .route( "/metrics", get(|| async move { metric_handle.render() }), diff --git a/webserver/src/dto/masp.rs b/webserver/src/dto/masp.rs new file mode 100644 index 000000000..503ea1ad1 --- /dev/null +++ b/webserver/src/dto/masp.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; +use validator::Validate; + +#[derive(Clone, Serialize, Deserialize, Validate)] +#[serde(rename_all = "camelCase")] +pub struct MaspAggregatesQueryParams { + pub token: Option, +} diff --git a/webserver/src/dto/mod.rs b/webserver/src/dto/mod.rs index 8229c698e..c451a7e3e 100644 --- a/webserver/src/dto/mod.rs +++ b/webserver/src/dto/mod.rs @@ -1,5 +1,6 @@ pub mod crawler_state; pub mod gas; pub mod governance; +pub mod masp; pub mod pos; pub mod transaction; diff --git a/webserver/src/error/api.rs b/webserver/src/error/api.rs index 4a826564f..b3afcdfa6 100644 --- a/webserver/src/error/api.rs +++ b/webserver/src/error/api.rs @@ -8,6 +8,7 @@ use super::crawler_state::CrawlerStateError; use super::gas::GasError; use super::governance::GovernanceError; use super::ibc::IbcError; +use super::masp::MaspError; use super::pos::PoSError; use super::revealed_pk::RevealedPkError; use super::transaction::TransactionError; @@ -33,6 +34,8 @@ pub enum ApiError { #[error(transparent)] IbcError(#[from] IbcError), #[error(transparent)] + MaspError(#[from] MaspError), + #[error(transparent)] CrawlerStateError(#[from] CrawlerStateError), } @@ -48,6 +51,7 @@ impl IntoResponse for ApiError { ApiError::RevealedPkError(error) => error.into_response(), ApiError::GasError(error) => error.into_response(), ApiError::IbcError(error) => error.into_response(), + ApiError::MaspError(error) => error.into_response(), ApiError::CrawlerStateError(error) => error.into_response(), } } diff --git a/webserver/src/error/masp.rs b/webserver/src/error/masp.rs new file mode 100644 index 000000000..c7da57c50 --- /dev/null +++ b/webserver/src/error/masp.rs @@ -0,0 +1,24 @@ +use axum::http::StatusCode; +use axum::response::IntoResponse; +use thiserror::Error; + +use crate::response::api::ApiErrorResponse; +#[derive(Error, Debug)] +pub enum MaspError { + #[error("Database error: {0}")] + Database(String), + #[error("Unknown error: {0}")] + Unknown(String), +} + +impl IntoResponse for MaspError { + fn into_response(self) -> axum::response::Response { + let status_code = match self { + MaspError::Unknown(_) | MaspError::Database(_) => { + StatusCode::INTERNAL_SERVER_ERROR + } + }; + + ApiErrorResponse::send(status_code.as_u16(), Some(self.to_string())) + } +} diff --git a/webserver/src/error/mod.rs b/webserver/src/error/mod.rs index e41db3de2..9a66fc5f0 100644 --- a/webserver/src/error/mod.rs +++ b/webserver/src/error/mod.rs @@ -6,6 +6,7 @@ pub mod crawler_state; pub mod gas; pub mod governance; pub mod ibc; +pub mod masp; pub mod pos; pub mod revealed_pk; pub mod transaction; diff --git a/webserver/src/handler/masp.rs b/webserver/src/handler/masp.rs new file mode 100644 index 000000000..cecc5947d --- /dev/null +++ b/webserver/src/handler/masp.rs @@ -0,0 +1,24 @@ +use axum::extract::State; +use axum::http::HeaderMap; +use axum::Json; +use axum_extra::extract::Query; +use axum_macros::debug_handler; + +use crate::dto::masp::MaspAggregatesQueryParams; +use crate::error::api::ApiError; +use crate::response::masp::MaspPoolAggregateResponse; +use crate::state::common::CommonState; + +#[debug_handler] +pub async fn get_masp_aggregates( + _headers: HeaderMap, + State(state): State, + Query(query): Query, +) -> Result>, ApiError> { + let masp_aggregates = state + .masp_service + .find_all_masp_aggregates(query.token) + .await?; + + Ok(Json(masp_aggregates)) +} diff --git a/webserver/src/handler/mod.rs b/webserver/src/handler/mod.rs index f48188102..fbc350b6e 100644 --- a/webserver/src/handler/mod.rs +++ b/webserver/src/handler/mod.rs @@ -5,6 +5,7 @@ pub mod crawler_state; pub mod gas; pub mod governance; pub mod ibc; +pub mod masp; pub mod pk; pub mod pos; pub mod transaction; diff --git a/webserver/src/repository/masp.rs b/webserver/src/repository/masp.rs new file mode 100644 index 000000000..28aaab4c6 --- /dev/null +++ b/webserver/src/repository/masp.rs @@ -0,0 +1,60 @@ +use axum::async_trait; +use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; +use orm::masp::MaspPoolDb; +use orm::schema::masp_pool_aggregate; + +use crate::appstate::AppState; + +#[derive(Clone)] +pub struct MaspRepository { + pub(crate) app_state: AppState, +} + +#[async_trait] +pub trait MaspRepositoryTrait { + fn new(app_state: AppState) -> Self; + + async fn find_all_aggregates(&self) -> Result, String>; + + async fn find_all_aggregates_by_token( + &self, + token: String, + ) -> Result, String>; +} + +#[async_trait] +impl MaspRepositoryTrait for MaspRepository { + fn new(app_state: AppState) -> Self { + Self { app_state } + } + + async fn find_all_aggregates(&self) -> Result, String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| { + masp_pool_aggregate::table + .select(MaspPoolDb::as_select()) + .load(conn) + }) + .await + .map_err(|e| e.to_string())? + .map_err(|e| e.to_string()) + } + + async fn find_all_aggregates_by_token( + &self, + token: String, + ) -> Result, String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| { + masp_pool_aggregate::table + .filter(masp_pool_aggregate::dsl::token_address.eq(token)) + .select(MaspPoolDb::as_select()) + .load(conn) + }) + .await + .map_err(|e| e.to_string())? + .map_err(|e| e.to_string()) + } +} diff --git a/webserver/src/repository/mod.rs b/webserver/src/repository/mod.rs index 448aefb63..4769a11cd 100644 --- a/webserver/src/repository/mod.rs +++ b/webserver/src/repository/mod.rs @@ -4,6 +4,7 @@ pub mod chain; pub mod gas; pub mod governance; pub mod ibc; +pub mod masp; pub mod pos; pub mod revealed_pk; pub mod tranasaction; diff --git a/webserver/src/response/masp.rs b/webserver/src/response/masp.rs new file mode 100644 index 000000000..c0cb10cf8 --- /dev/null +++ b/webserver/src/response/masp.rs @@ -0,0 +1,60 @@ +use orm::masp::{ + MaspPoolAggregateKindDb, MaspPoolAggregateWindowDb, MaspPoolDb, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum MaspPoolAggregateWindow { + OneDay, + SevenDays, + ThirtyDays, + AllTime, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum MaspPoolAggregateKind { + Inflows, + Outflows, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaspPoolAggregateResponse { + pub token_address: String, + pub time_window: MaspPoolAggregateWindow, + pub kind: MaspPoolAggregateKind, + pub total_amount: String, +} + +impl From for MaspPoolAggregateResponse { + fn from(value: MaspPoolDb) -> Self { + MaspPoolAggregateResponse { + token_address: value.token_address, + time_window: match value.time_window { + MaspPoolAggregateWindowDb::OneDay => { + MaspPoolAggregateWindow::OneDay + } + MaspPoolAggregateWindowDb::SevenDays => { + MaspPoolAggregateWindow::SevenDays + } + MaspPoolAggregateWindowDb::ThirtyDays => { + MaspPoolAggregateWindow::ThirtyDays + } + MaspPoolAggregateWindowDb::AllTime => { + MaspPoolAggregateWindow::AllTime + } + }, + kind: match value.kind { + MaspPoolAggregateKindDb::Inflows => { + MaspPoolAggregateKind::Inflows + } + MaspPoolAggregateKindDb::Outflows => { + MaspPoolAggregateKind::Outflows + } + }, + total_amount: value.total_amount.to_string(), + } + } +} diff --git a/webserver/src/response/mod.rs b/webserver/src/response/mod.rs index 980eb325d..634bb193f 100644 --- a/webserver/src/response/mod.rs +++ b/webserver/src/response/mod.rs @@ -6,6 +6,7 @@ pub mod crawler_state; pub mod gas; pub mod governance; pub mod ibc; +pub mod masp; pub mod pos; pub mod revealed_pk; pub mod transaction; diff --git a/webserver/src/service/masp.rs b/webserver/src/service/masp.rs new file mode 100644 index 000000000..2bc776dfa --- /dev/null +++ b/webserver/src/service/masp.rs @@ -0,0 +1,43 @@ +use crate::appstate::AppState; +use crate::error::masp::MaspError; +use crate::repository::masp::{MaspRepository, MaspRepositoryTrait}; +use crate::response::masp::MaspPoolAggregateResponse; + +#[derive(Clone)] +pub struct MaspService { + pub masp_repo: MaspRepository, +} + +impl MaspService { + pub fn new(app_state: AppState) -> Self { + Self { + masp_repo: MaspRepository::new(app_state), + } + } + + pub async fn find_all_masp_aggregates( + &self, + token: Option, + ) -> Result, MaspError> { + let masp_aggregates = match token { + Some(token) => self + .masp_repo + .find_all_aggregates_by_token(token) + .await + .map_err(MaspError::Database)? + .into_iter() + .map(MaspPoolAggregateResponse::from) + .collect(), + None => self + .masp_repo + .find_all_aggregates() + .await + .map_err(MaspError::Database)? + .into_iter() + .map(MaspPoolAggregateResponse::from) + .collect(), + }; + + Ok(masp_aggregates) + } +} diff --git a/webserver/src/service/mod.rs b/webserver/src/service/mod.rs index 1178a89f7..9d8a2084c 100644 --- a/webserver/src/service/mod.rs +++ b/webserver/src/service/mod.rs @@ -5,6 +5,7 @@ pub mod crawler_state; pub mod gas; pub mod governance; pub mod ibc; +pub mod masp; pub mod pos; pub mod revealed_pk; pub mod transaction; diff --git a/webserver/src/state/common.rs b/webserver/src/state/common.rs index f4a9363bd..b3007c53a 100644 --- a/webserver/src/state/common.rs +++ b/webserver/src/state/common.rs @@ -9,6 +9,7 @@ use crate::service::crawler_state::CrawlerStateService; use crate::service::gas::GasService; use crate::service::governance::GovernanceService; use crate::service::ibc::IbcService; +use crate::service::masp::MaspService; use crate::service::pos::PosService; use crate::service::revealed_pk::RevealedPkService; use crate::service::transaction::TransactionService; @@ -25,6 +26,7 @@ pub struct CommonState { pub transaction_service: TransactionService, pub crawler_state_service: CrawlerStateService, pub ibc_service: IbcService, + pub masp_service: MaspService, pub client: HttpClient, pub config: AppConfig, } @@ -42,6 +44,7 @@ impl CommonState { transaction_service: TransactionService::new(data.clone()), crawler_state_service: CrawlerStateService::new(data.clone()), ibc_service: IbcService::new(data.clone()), + masp_service: MaspService::new(data), client, config, }