diff --git a/rusk/CHANGELOG.md b/rusk/CHANGELOG.md index 7c5217c7b..2e699a86a 100644 --- a/rusk/CHANGELOG.md +++ b/rusk/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Change plonk verification to use embed verification data by default [#3507] +- Change responses for moonlight gql endpoints (archive node) [#3512] ## [1.1.0] - 2025-02-14 @@ -347,6 +348,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add build system that generates keys for circuits and caches them. +[#3512]: https://github.com/dusk-network/rusk/issues/3512 [#3507]: https://github.com/dusk-network/rusk/issues/3507 [#3494]: https://github.com/dusk-network/rusk/issues/3494 [#3481]: https://github.com/dusk-network/rusk/issues/3481 diff --git a/rusk/Cargo.toml b/rusk/Cargo.toml index f98f69c59..332d3015d 100644 --- a/rusk/Cargo.toml +++ b/rusk/Cargo.toml @@ -111,7 +111,7 @@ recovery-keys = ["rusk-recovery/keys"] prover = ["dep:rusk-prover"] testwallet = ["dep:futures"] chain = ["dep:node", "dep:dusk-consensus", "dep:node-data"] -archive = ["chain", "node/archive"] +archive = ["chain", "node/archive", "dusk-core/serde"] network-trace = ["node/network-trace"] http-wasm = [] dynamic-verifier = [] diff --git a/rusk/src/lib/http/chain/graphql.rs b/rusk/src/lib/http/chain/graphql.rs index b9f3d7435..0e0f89aa7 100644 --- a/rusk/src/lib/http/chain/graphql.rs +++ b/rusk/src/lib/http/chain/graphql.rs @@ -22,7 +22,6 @@ use node::database::{Ledger, DB}; use node_data::ledger::Label; #[cfg(feature = "archive")] use { - archive::data::deserialized_archive_data::DeserializedMoonlightGroups, archive::data::*, archive::events::*, archive::finalized_block::*, @@ -180,16 +179,24 @@ impl Query { }) } + /// Retrieves the events of all historical transactions that have been + /// affecting the moonlight balance of the given address. #[cfg(feature = "archive")] async fn full_moonlight_history( &self, ctx: &Context<'_>, address: String, ord: Option, - ) -> OptResult { + ) -> OptResult { full_moonlight_history(ctx, address, ord).await } + /// Retrieves raw events from transactions where at least one event within a + /// transaction indicates a transfer of funds. + /// + /// Filter by topic="moonlight" and target=TRANSFER_CONTRACT on events for a + /// tx, to get only events from moonlight transactions. (Instead of both + /// phoenix or moonlight transactions) #[allow(clippy::too_many_arguments)] #[cfg(feature = "archive")] async fn moonlight_history( @@ -223,7 +230,7 @@ impl Query { moonlight_tx_by_memo(ctx, memo).await } - /// Get contract events by height or hash. + /// Get contract events by block height or hash. #[cfg(feature = "archive")] async fn contract_events( &self, diff --git a/rusk/src/lib/http/chain/graphql/archive/data.rs b/rusk/src/lib/http/chain/graphql/archive/data.rs index d268f8571..64a0be324 100644 --- a/rusk/src/lib/http/chain/graphql/archive/data.rs +++ b/rusk/src/lib/http/chain/graphql/archive/data.rs @@ -8,9 +8,47 @@ use async_graphql::Object; use dusk_bytes::Serializable; use dusk_core::signatures::bls::PublicKey as AccountPublicKey; use node::archive::MoonlightGroup; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; +use translator::{IntermediateEvent, IntermediateMoonlightGroup}; +/// List of archived transactions where each transaction includes at least one +/// event indicating a Moonlight transfer of funds (Not necessarily a moonlight +/// transaction). pub struct MoonlightTransfers(pub Vec); +impl Serialize for MoonlightTransfers { + /// Serializes TRANSFER_CONTRACT events specifically as JSON, falling back + /// to hex encoding for other event types. + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut moonlight_groups: &Vec = &self.0; + let mut serializable_groups: Vec = vec![]; + + // yoink the events from the moonlight group + for group in moonlight_groups { + // convert the events of that group to intermediate events + let intermediate_events: Vec = group + .events() + .iter() + .map(|event| IntermediateEvent::from(event.clone())) + .collect(); + + // push the intermediate events to the serializable group + serializable_groups.push(IntermediateMoonlightGroup { + events: serde_json::to_value(intermediate_events) + .unwrap_or_default(), + origin: *group.origin(), + block_height: group.block_height(), + }); + } + + serializable_groups.serialize(serializer) + } +} + pub struct ContractEvents(pub(super) serde_json::Value); pub(super) struct NewAccountPublicKey(pub AccountPublicKey); @@ -34,7 +72,7 @@ impl TryInto for String { #[Object] impl MoonlightTransfers { pub async fn json(&self) -> serde_json::Value { - serde_json::to_value(&self.0).unwrap_or_default() + serde_json::to_value(self).unwrap_or_default() } } @@ -46,220 +84,164 @@ impl ContractEvents { } /// Interim solution for sending out deserialized event data -/// TODO: #2773 add serde feature to dusk-core -pub mod deserialized_archive_data { - use super::*; - +/// TODO: data driver should further simplify this +pub mod translator { use dusk_core::abi::ContractId; - use dusk_core::stake::STAKE_CONTRACT; + use dusk_core::stake::StakeEvent; + use dusk_core::stake::{Reward, SlashEvent, STAKE_CONTRACT}; use dusk_core::transfer::withdraw::WithdrawReceiver; use dusk_core::transfer::{ - ConvertEvent, DepositEvent, MoonlightTransactionEvent, WithdrawEvent, - CONVERT_TOPIC, DEPOSIT_TOPIC, MINT_TOPIC, MOONLIGHT_TOPIC, - TRANSFER_CONTRACT, WITHDRAW_TOPIC, + ContractToAccountEvent, ContractToContractEvent, ConvertEvent, + DepositEvent, MoonlightTransactionEvent, PhoenixTransactionEvent, + WithdrawEvent, CONTRACT_TO_ACCOUNT_TOPIC, CONTRACT_TO_CONTRACT_TOPIC, + CONVERT_TOPIC, DEPOSIT_TOPIC, MINT_CONTRACT_TOPIC, MINT_TOPIC, + MOONLIGHT_TOPIC, PHOENIX_TOPIC, TRANSFER_CONTRACT, WITHDRAW_TOPIC, }; use node_data::events::contract::{ContractEvent, OriginHash}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; + use super::*; + #[serde_with::serde_as] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] - pub struct DeserializedMoonlightGroup { + pub(super) struct IntermediateMoonlightGroup { pub events: serde_json::Value, #[serde_as(as = "serde_with::hex::Hex")] pub origin: OriginHash, pub block_height: u64, } - pub struct DeserializedMoonlightGroups(pub Vec); - - #[Object] - impl DeserializedMoonlightGroups { - pub async fn json(&self) -> serde_json::Value { - serde_json::to_value(&self.0).unwrap_or_default() - } - } - #[derive(Debug, Clone, PartialEq)] - pub struct DeserializedMoonlightTransactionEvent( - pub MoonlightTransactionEvent, - ); - - impl Serialize for DeserializedMoonlightTransactionEvent { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let moonlight_event = &self.0; - - let mut state = - serializer.serialize_struct("MoonlightTransactionEvent", 6)?; - state.serialize_field( - "sender", - &bs58::encode(moonlight_event.sender.to_bytes()).into_string(), - )?; - state.serialize_field( - "receiver", - &moonlight_event - .receiver - .map(|r| bs58::encode(r.to_bytes()).into_string()), - )?; - state.serialize_field("value", &moonlight_event.value)?; - state - .serialize_field("memo", &hex::encode(&moonlight_event.memo))?; - state.serialize_field("gas_spent", &moonlight_event.gas_spent)?; - state.serialize_field( - "refund_info", - &moonlight_event.refund_info.map(|(pk, amt)| { - (bs58::encode(pk.to_bytes()).into_string(), amt) - }), - )?; - - state.end() - } + /// Intermediate Event struct which can represent the data field in + /// different formats. + #[derive(Debug, Clone, PartialEq, Serialize)] + pub(super) struct IntermediateEvent { + pub target: ContractId, + pub topic: String, + pub data: serde_json::Value, } - #[derive(Debug, Clone, PartialEq)] - pub struct DeserializedWithdrawEvent(pub WithdrawEvent); - - impl Serialize for DeserializedWithdrawEvent { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let withdraw_event = &self.0; - let mut state = serializer.serialize_struct("WithdrawEvent", 3)?; - state.serialize_field("sender", &withdraw_event.sender)?; - state.serialize_field( - "receiver", - &match withdraw_event.receiver { - WithdrawReceiver::Moonlight(pk) => { - bs58::encode(pk.to_bytes()).into_string() - } - WithdrawReceiver::Phoenix(pk) => { - bs58::encode(pk.to_bytes()).into_string() - } - }, - )?; - state.serialize_field("value", &withdraw_event.value)?; + fn handle_unknown_genesis( + event_data: Vec, + ) -> Result { + tracing::warn!("Unknown genesis event found while calling translate_transfer_events"); - state.end() - } + serde_json::to_value(hex::encode(event_data)) } - #[derive(Debug, Clone, PartialEq)] - pub struct DeserializedConvertEvent(pub ConvertEvent); - - impl Serialize for DeserializedConvertEvent { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let convert_event = &self.0; - let mut state = serializer.serialize_struct("ConvertEvent", 3)?; - state.serialize_field( - "sender", - &convert_event - .sender - .map(|pk| bs58::encode(pk.to_bytes()).into_string()), - )?; - state.serialize_field( - "receiver", - &match convert_event.receiver { - WithdrawReceiver::Moonlight(pk) => { - bs58::encode(pk.to_bytes()).into_string() - } - WithdrawReceiver::Phoenix(pk) => { - bs58::encode(pk.to_bytes()).into_string() - } - }, - )?; - state.serialize_field("value", &convert_event.value)?; - state.end() + /// This function expects an event from the transfer contract. + /// + /// Otherwise it will return the hex encoded data. + fn translate_transfer_events( + transfer_contract_event: ContractEvent, + ) -> Result { + match transfer_contract_event.topic.as_str() { + MOONLIGHT_TOPIC => rkyv::from_bytes::( + &transfer_contract_event.data, + ) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }), + WITHDRAW_TOPIC | MINT_TOPIC => { + rkyv::from_bytes::(&transfer_contract_event.data) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }) + } + CONVERT_TOPIC => { + rkyv::from_bytes::(&transfer_contract_event.data) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }) + } + DEPOSIT_TOPIC => { + rkyv::from_bytes::(&transfer_contract_event.data) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }) + } + CONTRACT_TO_ACCOUNT_TOPIC => { + rkyv::from_bytes::( + &transfer_contract_event.data, + ) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }) + } + MINT_CONTRACT_TOPIC | CONTRACT_TO_CONTRACT_TOPIC => { + rkyv::from_bytes::( + &transfer_contract_event.data, + ) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }) + } + PHOENIX_TOPIC => rkyv::from_bytes::( + &transfer_contract_event.data, + ) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(transfer_contract_event.data) + }), + _ => handle_unknown_genesis(transfer_contract_event.data), } } - #[derive(Debug, Clone, PartialEq)] - pub struct DeserializedDepositEvent(pub DepositEvent); - - impl Serialize for DeserializedDepositEvent { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let deposit_event = &self.0; - let mut state = serializer.serialize_struct("DepositEvent", 3)?; - state.serialize_field( - "sender", - &deposit_event - .sender - .map(|pk| bs58::encode(pk.to_bytes()).into_string()), - )?; - state.serialize_field("receiver", &deposit_event.receiver)?; - state.serialize_field("value", &deposit_event.value)?; - - state.end() + /// This function expects an event from the stake contract. + /// + /// Otherwise it will return the hex encoded data. + fn translate_stake_events( + stake_contract_event: ContractEvent, + ) -> Result { + match stake_contract_event.topic.as_str() { + "stake" | "unstake" | "withdraw" => { + rkyv::from_bytes::(&stake_contract_event.data) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(stake_contract_event.data) + }) + } + "reward" => { + rkyv::from_bytes::>(&stake_contract_event.data) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(stake_contract_event.data) + }) + } + "slash" | "hard_slash" => { + rkyv::from_bytes::>(&stake_contract_event.data) + .map(serde_json::to_value) + .unwrap_or_else(|_| { + handle_unknown_genesis(stake_contract_event.data) + }) + } + _ => handle_unknown_genesis(stake_contract_event.data), } } - #[derive(Debug, Clone, PartialEq, Serialize)] - pub struct DeserializedContractEvent { - pub target: ContractId, - pub topic: String, - pub data: serde_json::Value, - } - - impl From for DeserializedContractEvent { + /// TODO: core should be able to provide this translation from bytes to + /// struct & from bytes to json for events? + impl From for IntermediateEvent { fn from(event: ContractEvent) -> Self { - let deserialized_data = if event.target == TRANSFER_CONTRACT { - match event.topic.as_str() { - MOONLIGHT_TOPIC => rkyv::from_bytes::< - MoonlightTransactionEvent, - >(&event.data) - .map(|e| { - serde_json::to_value( - DeserializedMoonlightTransactionEvent(e), - ) - }) - .unwrap_or_else(|_| serde_json::to_value(event.data)), - WITHDRAW_TOPIC | MINT_TOPIC => rkyv::from_bytes::< - WithdrawEvent, - >( - &event.data - ) - .map(|e| serde_json::to_value(DeserializedWithdrawEvent(e))) - .unwrap_or_else(|_| serde_json::to_value(event.data)), - CONVERT_TOPIC => { - rkyv::from_bytes::(&event.data) - .map(|e| { - serde_json::to_value(DeserializedConvertEvent( - e, - )) - }) - .unwrap_or_else(|_| { - serde_json::to_value(event.data) - }) - } - DEPOSIT_TOPIC => { - rkyv::from_bytes::(&event.data) - .map(|e| { - serde_json::to_value(DeserializedDepositEvent( - e, - )) - }) - .unwrap_or_else(|_| { - serde_json::to_value(event.data) - }) - } - _ => serde_json::to_value(hex::encode(event.data)), - } - } else { - serde_json::to_value(hex::encode(event.data)) + let target = event.target; + let topic = event.topic.clone(); + + let deserialized_data = match event.target { + TRANSFER_CONTRACT => translate_transfer_events(event), + STAKE_CONTRACT => translate_stake_events(event), + _ => serde_json::to_value(hex::encode(event.data)), } .unwrap_or_else(|e| serde_json::Value::String(e.to_string())); Self { - target: event.target, - topic: event.topic, + target, + topic, data: deserialized_data, } } diff --git a/rusk/src/lib/http/chain/graphql/archive/events.rs b/rusk/src/lib/http/chain/graphql/archive/events.rs index 29e56e014..bf4247109 100644 --- a/rusk/src/lib/http/chain/graphql/archive/events.rs +++ b/rusk/src/lib/http/chain/graphql/archive/events.rs @@ -6,11 +6,12 @@ //! Module for GraphQL that relates to stored events in the archive. -use super::data::ContractEvents; -use crate::http::chain::graphql::{DBContext, OptResult}; use async_graphql::{Context, FieldError, FieldResult, Object}; use dusk_core::abi::{ContractId, CONTRACT_ID_BYTES}; +use super::data::ContractEvents; +use crate::http::chain::graphql::{DBContext, OptResult}; + pub async fn events_by_height( ctx: &Context<'_>, height: i64, diff --git a/rusk/src/lib/http/chain/graphql/archive/moonlight.rs b/rusk/src/lib/http/chain/graphql/archive/moonlight.rs index 3d752bedc..603323ef8 100644 --- a/rusk/src/lib/http/chain/graphql/archive/moonlight.rs +++ b/rusk/src/lib/http/chain/graphql/archive/moonlight.rs @@ -18,7 +18,7 @@ use node_data::events::contract::ContractEvent; use async_graphql::{Context, FieldError}; -use super::data::deserialized_archive_data::*; +use super::data::translator::*; use super::data::{MoonlightTransfers, NewAccountPublicKey}; use crate::http::chain::graphql::{DBContext, OptResult}; @@ -26,7 +26,7 @@ pub async fn full_moonlight_history( ctx: &Context<'_>, address: String, ordering: Option, -) -> OptResult { +) -> OptResult { let (_, archive) = ctx.data::()?; let v = bs58::decode(address).into_vec()?; @@ -43,32 +43,10 @@ pub async fn full_moonlight_history( _ => None, }; - let moonlight_groups = archive.full_moonlight_history(pk, ord)?; - - let mut deser_moonlight_groups = Vec::new(); - - if let Some(moonlight_groups) = moonlight_groups { - for moonlight_group in moonlight_groups { - let deser_events = moonlight_group - .events() - .iter() - .map(|event| event.clone().into()) - .collect::>(); - - let deserialized_moonlight_group = DeserializedMoonlightGroup { - events: serde_json::to_value(deser_events)?, - origin: *moonlight_group.origin(), - block_height: moonlight_group.block_height(), - }; - - deser_moonlight_groups.push(deserialized_moonlight_group); - } - } - - if deser_moonlight_groups.is_empty() { - Ok(None) + if let Some(moonlight_events) = archive.full_moonlight_history(pk, ord)? { + Ok(Some(MoonlightTransfers(moonlight_events))) } else { - Ok(Some(DeserializedMoonlightGroups(deser_moonlight_groups))) + Ok(None) } }