diff --git a/node/.sqlx/query-dc6d1bfa595b7545e8a1bfdaa68f8b408b2bda1142934f2e36bd81135ab7b5c5.json b/node/.sqlx/query-dc6d1bfa595b7545e8a1bfdaa68f8b408b2bda1142934f2e36bd81135ab7b5c5.json new file mode 100644 index 0000000000..d13ff6f8e1 --- /dev/null +++ b/node/.sqlx/query-dc6d1bfa595b7545e8a1bfdaa68f8b408b2bda1142934f2e36bd81135ab7b5c5.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT json_contract_events FROM archive WHERE finalized = 1 ORDER BY block_height DESC LIMIT 1", + "describe": { + "columns": [ + { + "name": "json_contract_events", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + }, + "hash": "dc6d1bfa595b7545e8a1bfdaa68f8b408b2bda1142934f2e36bd81135ab7b5c5" +} diff --git a/node/README.md b/node/README.md index 8e57211796..c071c464f3 100644 --- a/node/README.md +++ b/node/README.md @@ -26,3 +26,5 @@ This can be done through: 1. Set DATABASE_URL or create .env file with ``DATABASE_URL=sqlite:///tmp/temp.sqlite3`` 2. Create a db with ``sqlx database create`` 3. Run the migrations with ``sqlx migrate run`` + +> NB: You need to be in the /node folder of this project for sqlx to detect the migrations folder diff --git a/node/src/archive/moonlight.rs b/node/src/archive/moonlight.rs index 9af8ed0c89..370d4f9303 100644 --- a/node/src/archive/moonlight.rs +++ b/node/src/archive/moonlight.rs @@ -8,6 +8,7 @@ use std::path::Path; use std::sync::Arc; use anyhow::{anyhow, Result}; +use core::result::Result as CoreResult; use dusk_bytes::Serializable; use execution_core::signatures::bls::PublicKey as AccountPublicKey; use node_data::events::contract::{ContractTxEvent, TxHash}; @@ -26,6 +27,7 @@ const MOONLIGHT_DB_FOLDER_NAME: &str = "moonlight.db"; // Column family names. const CF_TXHASH_MOONLIGHT_EVENTS: &str = "cf_txhash_mevents"; // TxHash to ContractMoonlightEvents mapping const CF_MOONLIGHT_ADDRESS_TXHASH: &str = "cf_maddress_txhash"; // AccountPublicKey to TxHash mapping +const CF_MEMO_TXHASH: &str = "cf_memo_txhash"; // Memo to TxHash mapping impl Archive { /// Create or open the moonlight database. @@ -73,6 +75,7 @@ impl Archive { CF_MOONLIGHT_ADDRESS_TXHASH, rocksdb_opts.clone(), ), + ColumnFamilyDescriptor::new(CF_MEMO_TXHASH, rocksdb_opts.clone()), ]; Arc::new( @@ -97,6 +100,12 @@ impl Archive { .ok_or(anyhow!("Column family not found")) } + fn cf_memo_txhash(&self) -> Result<&ColumnFamily> { + self.moonlight_db + .cf_handle(CF_MEMO_TXHASH) + .ok_or(anyhow!("Column family not found")) + } + /// Transform & Load moonlight related events into the moonlight database. /// /// # Arguments @@ -105,11 +114,15 @@ impl Archive { pub(super) fn tl_moonlight( &self, block_events: Vec, + block_height: u64, ) -> Result<()> { debug!("Loading moonlight transaction events into the moonlight db"); - let (address_mappings, _, moonlight_groups) = - transformer::group_by_origins_filter_and_convert(block_events); + let (address_mappings, memo_mappings, moonlight_groups) = + transformer::group_by_origins_filter_and_convert( + block_events, + block_height, + ); debug!("Found {} moonlight transactions", moonlight_groups.len()); @@ -127,6 +140,11 @@ impl Archive { )?; } + for memo_mapping in memo_mappings { + let (memo, tx_hash) = memo_mapping; + self.update_memo_txhash(memo, tx_hash)?; + } + Ok(()) } @@ -137,10 +155,10 @@ impl Archive { tx_hash: TxHash, ) -> Result<()> { let txn = self.moonlight_db.transaction(); - let cf = self.cf_moonlight_address_txhash()?; + let cf_address = self.cf_moonlight_address_txhash()?; let key = pk.to_bytes(); - let existing_tx_hashes = txn.get_cf(cf, key)?; + let existing_tx_hashes = txn.get_cf(cf_address, key)?; if let Some(tx_hashes) = existing_tx_hashes { let mut tx_hashes = @@ -150,14 +168,45 @@ impl Archive { tx_hashes.push(tx_hash); // Put the updated tx hashes back into the database - txn.put_cf(cf, key, serde_json::to_vec(&tx_hashes)?)?; + txn.put_cf(cf_address, key, serde_json::to_vec(&tx_hashes)?)?; txn.commit()?; Ok(()) } else { // Serialize the TxHash and put it into the database - txn.put_cf(cf, key, serde_json::to_vec(&vec![tx_hash])?)?; + txn.put_cf(cf_address, key, serde_json::to_vec(&vec![tx_hash])?)?; + + txn.commit()?; + + Ok(()) + } + } + + /// Insert or update a Memo to TxHash mapping. + fn update_memo_txhash(&self, memo: Vec, tx_hash: TxHash) -> Result<()> { + let txn = self.moonlight_db.transaction(); + let cf_memo = self.cf_memo_txhash()?; + let key = memo; + + let existing_tx_hashes = txn.get_cf(cf_memo, &key)?; + + if let Some(tx_hashes) = existing_tx_hashes { + let mut tx_hashes = + serde_json::from_slice::>(&tx_hashes)?; + + // Append the new TxHash to the existing tx hashes + tx_hashes.push(tx_hash); + + // Put the updated tx hashes back into the database + txn.put_cf(cf_memo, key, serde_json::to_vec(&tx_hashes)?)?; + + txn.commit()?; + + Ok(()) + } else { + // Serialize the TxHash and put it into the database + txn.put_cf(cf_memo, key, serde_json::to_vec(&vec![tx_hash])?)?; txn.commit()?; @@ -192,20 +241,6 @@ impl Archive { Ok(()) } - fn tx_hashes_multiget_key_tuple( - &self, - tx_hashes: Vec, - ) -> Result> { - let mut keys = Vec::with_capacity(tx_hashes.len()); - let cf = self.cf_txhash_moonlight_events()?; - - for tx_hash in tx_hashes { - keys.push((cf, tx_hash)); - } - - Ok(keys) - } - /// Get a vector of Vec for a given AccountPublicKey. /// /// Every MoonlightTxEvents is associated with a TxHash. @@ -215,12 +250,74 @@ impl Archive { /// This can be a "moonlight" event or /// a "withdraw", "mint" or "convert" event, where there is a Moonlight /// address as WithdrawReceiver - pub fn get_moonlight_events( + pub fn moonlight_txs_by_pk( &self, pk: AccountPublicKey, ) -> Result>> { if let Some(tx_hashes) = self.get_moonlight_tx_id(pk)? { - self.get_moonlight_events_by_tx_ids(tx_hashes) + self.moonlight_txs_by_txhash(tx_hashes) + } else { + Ok(None) + } + } + + /// Get a vector of Vec for a given memo. + /// + /// Clients are advised to check if the Vec is > 1, as memos are not unique. + pub fn moonlight_txs_by_memo( + &self, + memo: Vec, + ) -> Result>> { + if let Some(tx_hashes) = self.get_memo_tx_id(memo)? { + self.moonlight_txs_by_txhash(tx_hashes) + } else { + Ok(None) + } + } + + /// Get a vector of Vec for a given list of TxHash. + pub fn moonlight_txs_by_txhash( + &self, + tx_hashes: Vec, + ) -> Result>> { + let multi_get = self.multi_get_moonlight_events(tx_hashes)?; + + let mut deserialized_events = Vec::with_capacity(multi_get.len()); + + for serialized_event in multi_get { + if let Ok(Some(e)) = serialized_event { + deserialized_events + .push(serde_json::from_slice::(&e)?); + } else { + warn!("Serialized moonlight event not found"); + continue; + } + } + + Ok(Some(deserialized_events)) + } + + pub fn raw_moonlight_txs_by_pk( + &self, + pk: AccountPublicKey, + ) -> Result>>> { + if let Some(tx_hashes) = self.get_moonlight_tx_id(pk)? { + let multi_get = self.multi_get_moonlight_events(tx_hashes)?; + + // Convert the Vec>> into a Vec> by + // throwing away the errors and None values + let mut raw = Vec::with_capacity(multi_get.len()); + + for serialized_event in multi_get { + if let Ok(Some(e)) = serialized_event { + raw.push(e); + } else { + warn!("Serialized moonlight event not found"); + continue; + } + } + + Ok(Some(raw)) } else { Ok(None) } @@ -243,45 +340,58 @@ impl Archive { } } + /// Get a vector of TxHash that relate to moonlight + /// in- or outflows for a given memo. + pub fn get_memo_tx_id(&self, memo: Vec) -> Result>> { + let txn = self.moonlight_db.transaction(); + + if let Some(tx_hashes) = txn.get_cf(self.cf_memo_txhash()?, memo)? { + Ok(Some(serde_json::from_slice::>(&tx_hashes)?)) + } else { + Ok(None) + } + } + /// Get MoonlightTxEvents for a given TxHash. - pub fn get_moonlight_events_by_tx_id( + pub fn get_moonlight_events( &self, tx_id: TxHash, - ) -> Result> { + ) -> Result>> { let txn = self.moonlight_db.transaction(); if let Some(events) = txn.get_cf(self.cf_txhash_moonlight_events()?, tx_id)? { - Ok(Some(serde_json::from_slice::(&events)?)) + Ok(Some(events)) } else { Ok(None) } } + fn tx_hashes_multiget_key_tuple( + &self, + tx_hashes: Vec, + ) -> Result> { + let mut keys = Vec::with_capacity(tx_hashes.len()); + let cf = self.cf_txhash_moonlight_events()?; + + for tx_hash in tx_hashes { + keys.push((cf, tx_hash)); + } + + Ok(keys) + } + /// Get multiple MoonlightTxEvents for a given list of TxHash. - fn get_moonlight_events_by_tx_ids( + fn multi_get_moonlight_events( &self, tx_ids: Vec, - ) -> Result>> { + ) -> Result>, rocksdb::Error>>> { let txn = self.moonlight_db.transaction(); let keys = self.tx_hashes_multiget_key_tuple(tx_ids)?; - let serialized_events = txn.multi_get_cf(keys); - let mut contract_events = Vec::with_capacity(serialized_events.len()); - - for serialized_event in serialized_events { - if let Ok(Some(e)) = serialized_event { - contract_events - .push(serde_json::from_slice::(&e)?); - } else { - warn!("Serialized event not found"); - continue; - } - } - - Ok(Some(contract_events)) + Ok(txn.multi_get_cf(keys)) } } @@ -305,7 +415,7 @@ mod util { } if len != deduped.len() { - warn!("Found duplicates in address mappings for moonlight transactions"); + warn!("Found duplicates in address mappings for moonlight transactions. Duplicates have been removed. This is a bug."); } deduped @@ -321,7 +431,9 @@ mod tests { ConvertEvent, DepositEvent, MoonlightTransactionEvent, WithdrawEvent, }; use execution_core::{ContractId, CONTRACT_ID_BYTES}; - use node_data::events::contract::{ContractEvent, WrappedContractId}; + use node_data::events::contract::{ + ContractEvent, WrappedContractId, TX_HASH_BYTES, + }; use rand::{distributions::Alphanumeric, Rng}; use std::env; use std::path::PathBuf; @@ -393,12 +505,13 @@ mod tests { fn moonlight_event( origin: [u8; 32], receiver: Option, + memo: Vec, ) -> ContractTxEvent { let moonlight_tx_event = MoonlightTransactionEvent { sender: AccountPublicKey::default(), receiver, value: 500, - memo: vec![0, 1, 1, 0], + memo, gas_spent: 500, }; @@ -491,21 +604,39 @@ mod tests { deposit_event_moonlight([4; 32]), // should count (5 in total) convert_event_moonlight(), - moonlight_event([2; 32], Some(AccountPublicKey::default())), - moonlight_event([9; 32], Some(AccountPublicKey::default())), + moonlight_event( + [2; 32], + Some(AccountPublicKey::default()), + vec![0, 1, 1, 0], + ), + moonlight_event( + [9; 32], + Some(AccountPublicKey::default()), + vec![0, 1, 1, 0], + ), withdraw_event_moonlight(), // belongs together with deposit_event_phoenix - moonlight_event([5; 32], None), + moonlight_event([5; 32], None, vec![0, 1, 1, 0]), deposit_event_phoenix(), ] } + fn memo_txs() -> Vec { + vec![ + moonlight_event([0; 32], None, vec![0, 1, 1, 0]), + moonlight_event([1; 32], None, vec![0, 1, 1, 0]), + moonlight_event([2; 32], None, vec![0, 1, 1, 0]), + moonlight_event([3; 32], None, vec![0, 1, 1, 0]), + moonlight_event([4; 32], None, vec![1, 1, 1, 1]), + ] + } + #[tokio::test] async fn test_event_transformer() { let block_events = block_events(); let (mappings, _, moonlight_groups) = - group_by_origins_filter_and_convert(block_events); + group_by_origins_filter_and_convert(block_events, 1); // 5 moonlight groups means 5 transactions containing moonlight related // events @@ -519,64 +650,46 @@ mod tests { let archive = Archive::create_or_open(path).await; let pk = AccountPublicKey::default(); - assert!(archive.get_moonlight_events(pk).unwrap().is_none()); + assert!(archive.moonlight_txs_by_pk(pk).unwrap().is_none()); let block_events = block_events(); // Store block events in the archive - archive.tl_moonlight(block_events.clone()).unwrap(); + archive.tl_moonlight(block_events.clone(), 1).unwrap(); let fetched_tx_hashes = archive.get_moonlight_tx_id(pk).unwrap().unwrap(); - let fetched_events_by_tx_hash = archive - .get_moonlight_events_by_tx_ids(fetched_tx_hashes.clone()) - .unwrap() - .unwrap(); + let fetched_events_by_tx_hash = + archive.moonlight_txs_by_pk(pk).unwrap().unwrap(); assert_eq!(fetched_tx_hashes.len(), 5); - for contract_moonlight_events in fetched_events_by_tx_hash { - match contract_moonlight_events.origin().as_ref() { + for moonlight_events in fetched_events_by_tx_hash { + assert_eq!(moonlight_events.block_height(), 1); + + match moonlight_events.origin().as_ref() { [1, 1, ..] => { - assert_eq!(contract_moonlight_events.events().len(), 1); + assert_eq!(moonlight_events.events().len(), 1); - assert_eq!( - contract_moonlight_events.events()[0].topic, - "convert" - ); + assert_eq!(moonlight_events.events()[0].topic, "convert"); } [2, 2, ..] => { - assert_eq!(contract_moonlight_events.events().len(), 1); - assert_eq!( - contract_moonlight_events.events()[0].topic, - "moonlight" - ); + assert_eq!(moonlight_events.events().len(), 1); + assert_eq!(moonlight_events.events()[0].topic, "moonlight"); } [3, 3, ..] => { - assert_eq!(contract_moonlight_events.events().len(), 1); - assert_eq!( - contract_moonlight_events.events()[0].topic, - "withdraw" - ); + assert_eq!(moonlight_events.events().len(), 1); + assert_eq!(moonlight_events.events()[0].topic, "withdraw"); } [5, 5, ..] => { - assert_eq!(contract_moonlight_events.events().len(), 2); - assert_eq!( - contract_moonlight_events.events()[0].topic, - "moonlight" - ); - assert_eq!( - contract_moonlight_events.events()[1].topic, - "deposit" - ); + assert_eq!(moonlight_events.events().len(), 2); + assert_eq!(moonlight_events.events()[0].topic, "moonlight"); + assert_eq!(moonlight_events.events()[1].topic, "deposit"); } [9, 9, ..] => { - assert_eq!(contract_moonlight_events.events().len(), 1); - assert_eq!( - contract_moonlight_events.events()[0].topic, - "moonlight" - ); + assert_eq!(moonlight_events.events().len(), 1); + assert_eq!(moonlight_events.events()[0].topic, "moonlight"); } _ => panic!("unexpected TxHash"), } @@ -589,21 +702,81 @@ mod tests { let archive = Archive::create_or_open(path).await; let pk = AccountPublicKey::default(); - assert!(archive.get_moonlight_events(pk).unwrap().is_none()); + assert!(archive.moonlight_txs_by_pk(pk).unwrap().is_none()); let block_events = block_events(); - - archive.tl_moonlight(block_events.clone()).unwrap(); + archive.tl_moonlight(block_events.clone(), 1).unwrap(); let fetched_tx_hashes = archive.get_moonlight_tx_id(pk).unwrap().unwrap(); - let fetched_events_by_tx_hash = archive - .get_moonlight_events_by_tx_id(fetched_tx_hashes[0]) - .unwrap() + let fetched_events_by_tx_hash = + serde_json::from_slice::( + &archive + .get_moonlight_events(fetched_tx_hashes[0]) + .unwrap() + .unwrap(), + ) .unwrap(); + assert_eq!(fetched_events_by_tx_hash.block_height(), 1); + assert_eq!(fetched_events_by_tx_hash.events().len(), 1); assert_eq!(fetched_events_by_tx_hash.events()[0].topic, "convert"); } + + #[tokio::test] + async fn test_tl_memo_and_fetch_single() { + let path = test_dir(); + let archive = Archive::create_or_open(path).await; + + let block_events = memo_txs(); + archive.tl_moonlight(block_events.clone(), 1).unwrap(); + + let fetched_tx1 = archive + .moonlight_txs_by_memo(vec![1, 1, 1, 1]) + .unwrap() + .unwrap(); + assert_eq!(fetched_tx1.len(), 1); + assert_eq!(fetched_tx1[0].origin(), &[4; 32]); + fetched_tx1[0].events().iter().for_each(|e| { + assert_eq!(e.topic, "moonlight"); + assert_eq!( + e.target, + WrappedContractId(execution_core::transfer::TRANSFER_CONTRACT) + ); + + let moonlight_event = + rkyv::from_bytes::(&e.data).unwrap(); + assert_eq!(moonlight_event.memo, vec![1, 1, 1, 1]); + assert!(moonlight_event.receiver.is_none()); + assert_eq!(moonlight_event.sender, AccountPublicKey::default()); + }); + + let fetched_tx3 = archive + .moonlight_txs_by_memo(vec![0, 1, 1, 0]) + .unwrap() + .unwrap(); + assert_eq!(fetched_tx3.len(), 4); + + for (i, fetched_tx) in fetched_tx3.iter().enumerate() { + assert_eq!(fetched_tx.origin(), &[i as u8; TX_HASH_BYTES]); + fetched_tx.events().iter().for_each(|e| { + assert_eq!(e.topic, "moonlight"); + assert_eq!( + e.target, + WrappedContractId( + execution_core::transfer::TRANSFER_CONTRACT + ) + ); + + let moonlight_event = + rkyv::from_bytes::(&e.data) + .unwrap(); + assert_eq!(moonlight_event.memo, vec![0, 1, 1, 0]); + assert!(moonlight_event.receiver.is_none()); + assert_eq!(moonlight_event.sender, AccountPublicKey::default()); + }); + } + } } diff --git a/node/src/archive/sqlite.rs b/node/src/archive/sqlite.rs index 98c935fb96..239c654aab 100644 --- a/node/src/archive/sqlite.rs +++ b/node/src/archive/sqlite.rs @@ -14,7 +14,7 @@ use sqlx::{ sqlite::{SqliteConnectOptions, SqlitePool}, Pool, Sqlite, }; -use tracing::{info, warn}; +use tracing::{error, info, warn}; use crate::archive::transformer::MoonlightTxEvents; use crate::archive::{Archive, Archivist}; @@ -57,10 +57,8 @@ impl Archive { /// Fetch the json string of all vm events from a given block height pub async fn fetch_json_vm_events( &self, - block_height: u64, + block_height: i64, ) -> Result { - let block_height: i64 = block_height as i64; - let mut conn = self.sqlite_archive.acquire().await?; let events = sqlx::query!( @@ -71,6 +69,40 @@ impl Archive { Ok(events.json_contract_events) } + /// Fetch the json string of all vm events from the last finalized block + pub async fn fetch_json_last_vm_events(&self) -> Result { + let mut conn = self.sqlite_archive.acquire().await?; + + let events = sqlx::query!( + r#"SELECT json_contract_events FROM archive WHERE finalized = 1 ORDER BY block_height DESC LIMIT 1"# + ) + .fetch_one(&mut *conn) + .await?; + + Ok(events.json_contract_events) + } + + /* + todo: Implement fetch json last vm events where finalized = 0? + */ + + /// Fetch the json string of all vm events from a given block height + pub async fn fetch_json_vm_events_by_blk_hash( + &self, + hex_block_hash: &str, + ) -> Result { + let mut conn = self.sqlite_archive.acquire().await?; + + let events = sqlx::query!( + r#"SELECT json_contract_events FROM archive WHERE block_hash = ?"#, + hex_block_hash + ) + .fetch_one(&mut *conn) + .await?; + + Ok(events.json_contract_events) + } + /// Fetch all vm events from a given block hash pub async fn fetch_vm_events_by_blk_hash( &self, @@ -145,7 +177,7 @@ impl Archivist for Archive { current_block_height: u64, hex_block_hash: &str, ) -> Result<()> { - let block_height: i64 = current_block_height as i64; + let current_block_height: i64 = current_block_height as i64; let mut conn = self.sqlite_archive.acquire().await?; @@ -161,14 +193,22 @@ impl Archivist for Archive { .await?; info!( - "Marked block {} as finalized at height {}. After {} blocks", + "Marked block {} with height {} as finalized. After {} blocks at height {}", util::truncate_string(hex_block_hash), - block_height, - block_height - r.block_height + r.block_height, + (current_block_height - r.block_height), + current_block_height ); + if r.block_height < 0 { + error!("Block height is negative. This is a bug."); + } + // Get the MoonlightTxEvents and load it into the moonlight db - self.tl_moonlight(serde_json::from_str(&r.json_contract_events)?)?; + self.tl_moonlight( + serde_json::from_str(&r.json_contract_events)?, + r.block_height as u64, + )?; Ok(()) } @@ -215,7 +255,7 @@ impl Archivist for Archive { address: AccountPublicKey, ) -> Result>> { // Get the moonlight events for the given public key from rocksdb - self.get_moonlight_events(address) + self.moonlight_txs_by_pk(address) } } diff --git a/node/src/archive/transformer.rs b/node/src/archive/transformer.rs index 7373055214..65d2907b37 100644 --- a/node/src/archive/transformer.rs +++ b/node/src/archive/transformer.rs @@ -23,12 +23,21 @@ pub struct MoonlightTxEvents { events: Vec, #[serde_as(as = "serde_with::hex::Hex")] origin: TxHash, + block_height: u64, } impl MoonlightTxEvents { // Private on purpose - fn new(events: Vec, origin: TxHash) -> Self { - Self { events, origin } + fn new( + events: Vec, + origin: TxHash, + block_height: u64, + ) -> Self { + Self { + events, + origin, + block_height, + } } pub fn events(&self) -> &Vec { @@ -38,6 +47,10 @@ impl MoonlightTxEvents { pub fn origin(&self) -> &TxHash { &self.origin } + + pub fn block_height(&self) -> u64 { + self.block_height + } } type AddressMapping = (AccountPublicKey, TxHash); @@ -49,6 +62,7 @@ type MemoMapping = (Vec, TxHash); /// Returns the address mappings, memo mappings and groups pub(super) fn group_by_origins_filter_and_convert( block_events: Vec, + block_height: u64, ) -> ( Vec, Vec, @@ -150,7 +164,11 @@ pub(super) fn group_by_origins_filter_and_convert( }); if is_moonlight { - moonlight_tx_groups.push(MoonlightTxEvents::new(group, tx_hash)); + moonlight_tx_groups.push(MoonlightTxEvents::new( + group, + tx_hash, + block_height, + )); } } diff --git a/rusk/src/lib/http/chain/graphql.rs b/rusk/src/lib/http/chain/graphql.rs index 92b69b8711..47fdbccdf4 100644 --- a/rusk/src/lib/http/chain/graphql.rs +++ b/rusk/src/lib/http/chain/graphql.rs @@ -135,4 +135,38 @@ impl Query { ) -> OptResult { mempool_by_hash(ctx, hash).await } + + #[cfg(feature = "archive")] + async fn full_moonlight_history( + &self, + ctx: &Context<'_>, + address: String, + ) -> OptResult { + moonlight_tx_by_address(ctx, address).await + } + + #[cfg(feature = "archive")] + async fn transactions_by_memo( + &self, + ctx: &Context<'_>, + memo: String, + ) -> OptResult { + // convert String to Vec + let memo = memo.into_bytes(); + moonlight_tx_by_memo(ctx, memo).await + } + + #[cfg(feature = "archive")] + async fn block_events( + &self, + ctx: &Context<'_>, + height: Option, + hash: Option, + ) -> OptResult { + match (height, hash) { + (Some(height), None) => block_events_by_height(ctx, height).await, + (None, Some(hash)) => block_events_by_hash(ctx, hash).await, + _ => Err(FieldError::new("Specify height or hash")), + } + } } diff --git a/rusk/src/lib/http/chain/graphql/block.rs b/rusk/src/lib/http/chain/graphql/block.rs index 52c7681b3e..e44d7e4baa 100644 --- a/rusk/src/lib/http/chain/graphql/block.rs +++ b/rusk/src/lib/http/chain/graphql/block.rs @@ -105,3 +105,38 @@ pub async fn blocks_range( blocks.reverse(); Ok(blocks) } + +#[cfg(feature = "archive")] +pub(super) async fn block_events_by_height( + ctx: &Context<'_>, + height: i64, +) -> OptResult { + let (_, archive) = ctx.data::()?; + let mut events; + + if height < 0 { + events = archive.fetch_json_last_vm_events().await.map_err(|e| { + FieldError::new(format!("Cannot fetch events: {}", e)) + })?; + } else { + events = archive.fetch_json_vm_events(height).await.map_err(|e| { + FieldError::new(format!("Cannot fetch events: {}", e)) + })?; + } + + Ok(Some(BlockEvents(serde_json::from_str(&events)?))) +} + +#[cfg(feature = "archive")] +pub(super) async fn block_events_by_hash( + ctx: &Context<'_>, + hash: String, +) -> OptResult { + let (_, archive) = ctx.data::()?; + let events = archive + .fetch_json_vm_events_by_blk_hash(&hash) + .await + .map_err(|e| FieldError::new(format!("Cannot fetch events: {}", e)))?; + + Ok(Some(BlockEvents(serde_json::from_str(&events)?))) +} diff --git a/rusk/src/lib/http/chain/graphql/data.rs b/rusk/src/lib/http/chain/graphql/data.rs index 1e99abc454..840a8a1a0f 100644 --- a/rusk/src/lib/http/chain/graphql/data.rs +++ b/rusk/src/lib/http/chain/graphql/data.rs @@ -32,6 +32,10 @@ impl Block { pub struct Header<'a>(&'a node_data::ledger::Header); pub struct SpentTransaction(pub node_data::ledger::SpentTransaction); pub struct Transaction<'a>(TransactionData<'a>); +#[cfg(feature = "archive")] +pub struct MoonlightTransactions(pub Vec); +#[cfg(feature = "archive")] +pub struct BlockEvents(pub(super) serde_json::Value); impl<'a> From<&'a node_data::ledger::Transaction> for Transaction<'a> { fn from(value: &'a node_data::ledger::Transaction) -> Self { @@ -170,6 +174,22 @@ impl Header<'_> { } } +#[cfg(feature = "archive")] +#[Object] +impl MoonlightTransactions { + pub async fn json(&self) -> serde_json::Value { + serde_json::to_value(&self.0).unwrap_or_default() + } +} + +#[cfg(feature = "archive")] +#[Object] +impl BlockEvents { + pub async fn json(&self) -> serde_json::Value { + self.0.clone() + } +} + #[Object] impl SpentTransaction { pub async fn tx(&self) -> Transaction { diff --git a/rusk/src/lib/http/chain/graphql/tx.rs b/rusk/src/lib/http/chain/graphql/tx.rs index d7f0eefee8..39ec5b9e85 100644 --- a/rusk/src/lib/http/chain/graphql/tx.rs +++ b/rusk/src/lib/http/chain/graphql/tx.rs @@ -6,6 +6,12 @@ use node::database::rocksdb::MD_HASH_KEY; use node::database::{Mempool, Metadata}; +#[cfg(feature = "archive")] +use { + dusk_bytes::Serializable, + execution_core::signatures::bls::PublicKey as AccountPublicKey, + node::archive::MoonlightTxEvents, +}; use super::*; @@ -77,3 +83,47 @@ pub async fn mempool_by_hash<'a>( let tx = db.read().await.view(|t| t.get_tx(hash))?; Ok(tx.map(|t| t.into())) } + +#[cfg(feature = "archive")] +pub(super) async fn moonlight_tx_by_address( + ctx: &Context<'_>, + address: String, +) -> OptResult { + use dusk_bytes::ParseHexStr; + + let (_, archive) = ctx.data::()?; + let v = bs58::decode(address).into_vec()?; + + let pk_bytes: [u8; 96] = v + .try_into() + .map_err(|_| FieldError::new("Invalid public key length"))?; + + let pk = AccountPublicKey::from_bytes(&pk_bytes) + .map_err(|_| anyhow::anyhow!("Failed to serialize given public key"))?; + + let moonlight_events = archive.moonlight_txs_by_pk(pk)?; + + if let Some(moonlight_events) = moonlight_events { + Ok(Some(MoonlightTransactions(moonlight_events))) + } else { + Ok(None) + } +} + +#[cfg(feature = "archive")] +pub(super) async fn moonlight_tx_by_memo( + ctx: &Context<'_>, + memo: Vec, +) -> OptResult { + use dusk_bytes::ParseHexStr; + + let (_, archive) = ctx.data::()?; + + let moonlight_events = archive.moonlight_txs_by_memo(memo)?; + + if let Some(moonlight_events) = moonlight_events { + Ok(Some(MoonlightTransactions(moonlight_events))) + } else { + Ok(None) + } +}