From efd993b112c100eb8cb6e5d80fd0a14a1e8ba882 Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Fri, 28 Mar 2025 16:35:49 +0200 Subject: [PATCH 01/10] fix(cat-gateway): Fixing indexing issues and chain follower synchronisation (#2100) * update cardano sync code * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix fmt * change schema version * wip * wip * wip * wip --- catalyst-gateway/bin/Cargo.toml | 12 +-- catalyst-gateway/bin/src/cardano/mod.rs | 22 ++--- .../bin/src/db/index/block/certs.rs | 62 +++++++------ .../bin/src/db/index/block/cip36/mod.rs | 89 +++++++++++++++---- .../block/cql/insert_stake_registration.cql | 2 + .../bin/src/db/index/block/mod.rs | 2 +- .../bin/src/db/index/queries/mod.rs | 14 +-- .../index/schema/cql/stake_registration.cql | 5 +- .../bin/src/db/index/schema/mod.rs | 2 +- .../bin/src/db/index/tests/scylla_purge.rs | 11 ++- .../service/api/documents/templates/mod.rs | 1 - .../src/service/common/auth/rbac/scheme.rs | 5 +- 12 files changed, 146 insertions(+), 81 deletions(-) diff --git a/catalyst-gateway/bin/Cargo.toml b/catalyst-gateway/bin/Cargo.toml index 55f3009f16f..b257dd019c7 100644 --- a/catalyst-gateway/bin/Cargo.toml +++ b/catalyst-gateway/bin/Cargo.toml @@ -15,12 +15,12 @@ repository.workspace = true workspace = true [dependencies] -cardano-chain-follower = { version = "0.0.8", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250317-00" } -rbac-registration = { version = "0.0.4", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250317-00" } -catalyst-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250317-00" } -cardano-blockchain-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250317-00" } -catalyst-signed-doc = { version = "0.0.4", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250317-00" } -c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250317-00" } +cardano-chain-follower = { version = "0.0.8", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250325-00" } +rbac-registration = { version = "0.0.4", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250325-00" } +catalyst-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250325-00" } +cardano-blockchain-types = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250325-00" } +catalyst-signed-doc = { version = "0.0.4", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250325-00" } +c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "r20250325-00" } pallas = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" } pallas-traverse = { version = "0.30.1", git = "https://github.com/input-output-hk/catalyst-pallas.git", rev = "9b5183c8b90b90fe2cc319d986e933e9518957b3" } diff --git a/catalyst-gateway/bin/src/cardano/mod.rs b/catalyst-gateway/bin/src/cardano/mod.rs index 508e21d056b..fe3e22ec2ae 100644 --- a/catalyst-gateway/bin/src/cardano/mod.rs +++ b/catalyst-gateway/bin/src/cardano/mod.rs @@ -185,17 +185,16 @@ impl SyncParams { } /// Convert Params into the result of the sync. - fn done( + pub(crate) fn done( &self, first: Option, first_immutable: bool, last: Option, last_immutable: bool, synced: u64, result: anyhow::Result<()>, ) -> Self { - if result.is_ok() && first_immutable && last_immutable { + if result.is_ok() && self.end != Point::TIP { // Update sync status in the Immutable DB. // Can fire and forget, because failure to update DB will simply cause the chunk to be // re-indexed, on recovery. update_sync_status(self.end.slot_or_default(), self.start.slot_or_default()); } - let mut done = self.clone(); done.first_indexed_block = first; done.first_is_immutable = first_immutable; @@ -203,7 +202,6 @@ impl SyncParams { done.last_is_immutable = last_immutable; done.total_blocks_synced = done.total_blocks_synced.saturating_add(synced); done.last_blocks_synced = synced; - done.result = Arc::new(Some(result)); done @@ -265,7 +263,7 @@ fn sync_subchain( // What we need to do here is tell the primary follower to start a new sync // for the new immutable data, and then purge the volatile database of the // old data (after the immutable data has synced). - info!(chain=%params.chain, "Immutable chain rolled forward."); + info!(chain=%params.chain, point=?chain_update.block_data().point(), "Immutable chain rolled forward."); let mut result = params.done( first_indexed_block, first_immutable, @@ -275,9 +273,10 @@ fn sync_subchain( Ok(()), ); // Signal the point the immutable chain rolled forward to. + // If this is live chain immediately stops to later run immutable sync tasks result.follower_roll_forward = Some(chain_update.block_data().point()); return result; - }; + } }, cardano_chain_follower::Kind::Block => { let block = chain_update.block_data(); @@ -311,8 +310,9 @@ fn sync_subchain( let purge_condition = PurgeCondition::PurgeForwards(rollback_slot); if let Err(error) = roll_forward::purge_live_index(purge_condition).await { - error!(chain=%params.chain, error=%error, "Chain follower - rollback, purging volatile data task failed."); + error!(chain=%params.chain, error=%error, + "Chain follower rollback, purging volatile data task failed." + ); } else { // How many slots are purged #[allow(clippy::arithmetic_side_effects)] @@ -364,7 +364,7 @@ fn sync_subchain( Ok(()), ); - info!(chain = %params.chain, result=%result, "Indexing Blockchain Completed: OK"); + info!(chain = %result.chain, result=%result, "Indexing Blockchain Completed: OK"); result }) @@ -539,7 +539,7 @@ impl SyncTask { }, Err(error) => { error!(chain=%self.cfg.chain, report=%finished, error=%error, - "An Immutable follower failed, restarting it."); + "An Immutable follower failed, restarting it."); // Restart the Immutable Chain sync task again from where it left // off. self.sync_tasks.push(sync_subchain( @@ -550,7 +550,7 @@ impl SyncTask { } } else { error!(chain=%self.cfg.chain, report=%finished, - "BUG: The Immutable follower completed, but without a proper result."); + "BUG: The Immutable follower completed, but without a proper result."); } }, Err(error) => { diff --git a/catalyst-gateway/bin/src/db/index/block/certs.rs b/catalyst-gateway/bin/src/db/index/block/certs.rs index fc070cf4df9..d29614e8eee 100644 --- a/catalyst-gateway/bin/src/db/index/block/certs.rs +++ b/catalyst-gateway/bin/src/db/index/block/certs.rs @@ -19,7 +19,7 @@ use crate::{ settings::cassandra_db, }; -/// Insert TXI Query and Parameters +/// Insert stake registration query #[derive(SerializeRow)] pub(crate) struct StakeRegistrationInsertQuery { /// Stake address (29 bytes). @@ -29,23 +29,22 @@ pub(crate) struct StakeRegistrationInsertQuery { /// Transaction Index. txn_index: DbTxnIndex, /// Full Stake Public Key (32 byte Ed25519 Public key, not hashed). - stake_public_key: MaybeUnset, + stake_public_key: DbPublicKey, /// Is the stake address a script or not. script: bool, - /// Is the Certificate Registered? + /// Is the Cardano Certificate Registered register: MaybeUnset, - /// Is the Certificate Deregistered? + /// Is the Cardano Certificate Deregistered deregister: MaybeUnset, + /// Is the stake address contains CIP36 registration? + cip36: MaybeUnset, /// Pool Delegation Address pool_delegation: MaybeUnset>, } impl Debug for StakeRegistrationInsertQuery { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let stake_public_key = match self.stake_public_key { - MaybeUnset::Unset => "UNSET", - MaybeUnset::Set(ref v) => &hex::encode(v.as_ref()), - }; + let stake_public_key = hex::encode(self.stake_public_key.as_ref()); let register = match self.register { MaybeUnset::Unset => "UNSET", MaybeUnset::Set(v) => &format!("{v:?}"), @@ -54,6 +53,10 @@ impl Debug for StakeRegistrationInsertQuery { MaybeUnset::Unset => "UNSET", MaybeUnset::Set(v) => &format!("{v:?}"), }; + let cip36 = match self.cip36 { + MaybeUnset::Unset => "UNSET", + MaybeUnset::Set(v) => &format!("{v:?}"), + }; let pool_delegation = match self.pool_delegation { MaybeUnset::Unset => "UNSET", MaybeUnset::Set(ref v) => &hex::encode(v), @@ -67,29 +70,28 @@ impl Debug for StakeRegistrationInsertQuery { .field("script", &self.script) .field("register", ®ister) .field("deregister", &deregister) + .field("cip36", &cip36) .field("pool_delegation", &pool_delegation) .finish() } } -/// TXI by Txn hash Index +/// Insert stake registration const INSERT_STAKE_REGISTRATION_QUERY: &str = include_str!("./cql/insert_stake_registration.cql"); impl StakeRegistrationInsertQuery { /// Create a new Insert Query. - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] pub fn new( stake_address: StakeAddress, slot_no: Slot, txn_index: TxnIndex, - stake_public_key: Option, script: bool, register: bool, deregister: bool, - pool_delegation: Option>, + stake_public_key: VerifyingKey, script: bool, register: bool, deregister: bool, + cip36: bool, pool_delegation: Option>, ) -> Self { - let stake_public_key = - stake_public_key.map_or(MaybeUnset::Unset, |a| MaybeUnset::Set(a.into())); StakeRegistrationInsertQuery { stake_address: stake_address.into(), slot_no: slot_no.into(), txn_index: txn_index.into(), - stake_public_key, + stake_public_key: stake_public_key.into(), script, register: if register { MaybeUnset::Set(true) @@ -101,6 +103,11 @@ impl StakeRegistrationInsertQuery { } else { MaybeUnset::Unset }, + cip36: if cip36 { + MaybeUnset::Set(true) + } else { + MaybeUnset::Unset + }, pool_delegation: if let Some(pool_delegation) = pool_delegation { MaybeUnset::Set(pool_delegation) } else { @@ -109,7 +116,7 @@ impl StakeRegistrationInsertQuery { } } - /// Prepare Batch of Insert TXI Index Data Queries + /// Prepare Batch of Insert stake registration. pub(crate) async fn prepare_batch( session: &Arc, cfg: &cassandra_db::EnvVars, ) -> anyhow::Result { @@ -184,16 +191,19 @@ impl CertInsertQuery { } // This may not be witnessed, its normal but disappointing. - self.stake_reg_data.push(StakeRegistrationInsertQuery::new( - stake_address, - slot_no, - txn, - pubkey, - script, - register, - deregister, - delegation, - )); + if let Some(pubkey) = pubkey { + self.stake_reg_data.push(StakeRegistrationInsertQuery::new( + stake_address, + slot_no, + txn, + pubkey, + script, + register, + deregister, + false, + delegation, + )); + } } /// Index an Alonzo Era certificate into the database. diff --git a/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs b/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs index 669f3d0aa93..eaed97ab8fb 100644 --- a/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs +++ b/catalyst-gateway/bin/src/db/index/block/cip36/mod.rs @@ -6,9 +6,11 @@ pub(crate) mod insert_cip36_invalid; use std::sync::Arc; -use cardano_blockchain_types::{Cip36, MultiEraBlock, Slot, TxnIndex}; +use cardano_blockchain_types::{Cip36, MultiEraBlock, Slot, StakeAddress, TxnIndex}; +use catalyst_types::hashes::Blake2b224Hash; use scylla::Session; +use super::certs; use crate::{ db::index::{ queries::{FallibleQueryTasks, PreparedQuery, SizedBatch}, @@ -25,6 +27,8 @@ pub(crate) struct Cip36InsertQuery { invalid: Vec, /// Stake Registration Data captured during indexing. for_vote_key: Vec, + /// Stake Registration Data captured during indexing. + stake_regs: Vec, } impl Cip36InsertQuery { @@ -34,6 +38,7 @@ impl Cip36InsertQuery { registrations: Vec::new(), invalid: Vec::new(), for_vote_key: Vec::new(), + stake_regs: Vec::new(), } } @@ -46,7 +51,10 @@ impl Cip36InsertQuery { insert_cip36_invalid::Params::prepare_batch(session, cfg).await; let insert_cip36_for_vote_key_addr_batch = insert_cip36_for_vote_key::Params::prepare_batch(session, cfg).await; - + // Its a hack of inserting `stake_regs` during the indexing CIP 36 registrations. + // Its done because some of the CIP 36 registrations contains some stake addresses which + // are not actually some how registered using cardano certs. + // Preparation of the `stake_regs` batch done under the `certs.rs` Ok(( insert_cip36_batch?, insert_cip36_invalid_batch?, @@ -55,28 +63,49 @@ impl Cip36InsertQuery { } /// Index the CIP-36 registrations in a transaction. - pub(crate) fn index(&mut self, index: TxnIndex, slot_no: Slot, block: &MultiEraBlock) { + pub(crate) fn index( + &mut self, index: TxnIndex, slot_no: Slot, block: &MultiEraBlock, + ) -> anyhow::Result<()> { // Catalyst strict is set to true match Cip36::new(block, index, true) { // Check for CIP-36 validity and should be strict catalyst (only 1 voting key) // Note that in `validate_voting_keys` we already checked if the array has only one - Ok(Some(cip36)) if cip36.is_valid() && cip36.is_cip36().unwrap_or_default() => { + Ok(Some(cip36)) if cip36.is_valid() => { // This should always pass, because we already checked if the array has only one - if let Some(voting_key) = cip36.voting_pks().first() { - self.registrations.push(insert_cip36::Params::new( - voting_key, slot_no, index, &cip36, + let voting_key = cip36.voting_pks().first().ok_or(anyhow::anyhow!( + "Valid CIP36 registration must have one voting key" + ))?; + + let stake_pk = cip36.stake_pk().ok_or(anyhow::anyhow!( + "Valid CIP36 registration must have one stake public key" + ))?; + let stake_pk_hash = Blake2b224Hash::new(&stake_pk.to_bytes()); + let stake_address = StakeAddress::new(block.network(), false, stake_pk_hash); + + self.registrations.push(insert_cip36::Params::new( + voting_key, slot_no, index, &cip36, + )); + self.for_vote_key + .push(insert_cip36_for_vote_key::Params::new( + voting_key, slot_no, index, &cip36, true, + )); + self.stake_regs + .push(certs::StakeRegistrationInsertQuery::new( + stake_address, + slot_no, + index, + *stake_pk, + false, + false, + false, + true, + None, )); - - self.for_vote_key - .push(insert_cip36_for_vote_key::Params::new( - voting_key, slot_no, index, &cip36, true, - )); - } }, // Invalid CIP-36 Registration - Ok(Some(cip36)) if cip36.is_cip36().unwrap_or_default() => { + Ok(Some(cip36)) => { // Cannot index an invalid CIP36, if there is no stake public key. - if cip36.stake_pk().is_some() { + if let Some(stake_pk) = cip36.stake_pk() { if cip36.voting_pks().is_empty() { self.invalid.push(insert_cip36_invalid::Params::new( None, slot_no, index, &cip36, @@ -95,10 +124,26 @@ impl Cip36InsertQuery { )); } } + + let stake_pk_hash = Blake2b224Hash::new(&stake_pk.to_bytes()); + let stake_address = StakeAddress::new(block.network(), false, stake_pk_hash); + self.stake_regs + .push(certs::StakeRegistrationInsertQuery::new( + stake_address, + slot_no, + index, + *stake_pk, + false, + false, + false, + true, + None, + )); } }, _ => {}, } + Ok(()) } /// Execute the CIP-36 Registration Indexing Queries. @@ -136,13 +181,22 @@ impl Cip36InsertQuery { query_handles.push(tokio::spawn(async move { inner_session .execute_batch( - PreparedQuery::Cip36RegistrationForStakeAddrInsertQuery, + PreparedQuery::Cip36RegistrationForVoteKeyInsertQuery, self.for_vote_key, ) .await })); } + if !self.stake_regs.is_empty() { + let inner_session = session.clone(); + query_handles.push(tokio::spawn(async move { + inner_session + .execute_batch(PreparedQuery::StakeRegistrationInsertQuery, self.stake_regs) + .await + })); + } + query_handles } } @@ -156,9 +210,10 @@ mod tests { fn index() { let block = test_utils::block_2(); let mut query = Cip36InsertQuery::new(); - query.index(0.into(), 0.into(), &block); + query.index(0.into(), 0.into(), &block).unwrap(); assert_eq!(1, query.registrations.len()); assert!(query.invalid.is_empty()); assert_eq!(1, query.for_vote_key.len()); + assert_eq!(1, query.stake_regs.len()); } } diff --git a/catalyst-gateway/bin/src/db/index/block/cql/insert_stake_registration.cql b/catalyst-gateway/bin/src/db/index/block/cql/insert_stake_registration.cql index 5523f0599a6..fb3a08e8261 100644 --- a/catalyst-gateway/bin/src/db/index/block/cql/insert_stake_registration.cql +++ b/catalyst-gateway/bin/src/db/index/block/cql/insert_stake_registration.cql @@ -7,6 +7,7 @@ INSERT INTO stake_registration ( script, register, deregister, + cip36, pool_delegation ) VALUES ( :stake_address, @@ -16,5 +17,6 @@ INSERT INTO stake_registration ( :script, :register, :deregister, + :cip36, :pool_delegation ); diff --git a/catalyst-gateway/bin/src/db/index/block/mod.rs b/catalyst-gateway/bin/src/db/index/block/mod.rs index bdb9f4a5c90..648cc351933 100644 --- a/catalyst-gateway/bin/src/db/index/block/mod.rs +++ b/catalyst-gateway/bin/src/db/index/block/mod.rs @@ -46,7 +46,7 @@ pub(crate) async fn index_block(block: &MultiEraBlock) -> anyhow::Result<()> { // let mint = txs.mints().iter() {}; // TODO: Index Metadata. - cip36_index.index(index, slot_no, block); + cip36_index.index(index, slot_no, block)?; // Index Certificates inside the transaction. cert_index.index(&txn, slot_no, index, block); diff --git a/catalyst-gateway/bin/src/db/index/queries/mod.rs b/catalyst-gateway/bin/src/db/index/queries/mod.rs index 48b1db5d6f2..f2de302702a 100644 --- a/catalyst-gateway/bin/src/db/index/queries/mod.rs +++ b/catalyst-gateway/bin/src/db/index/queries/mod.rs @@ -65,8 +65,8 @@ pub(crate) enum PreparedQuery { Cip36RegistrationInsertQuery, /// CIP 36 Registration Error Insert query. Cip36RegistrationInsertErrorQuery, - /// CIP 36 Registration for stake address Insert query. - Cip36RegistrationForStakeAddrInsertQuery, + /// CIP 36 Registration for voting key Insert query. + Cip36RegistrationForVoteKeyInsertQuery, /// TXO spent Update query. TxoSpentUpdateQuery, /// RBAC 509 Registration Insert query. @@ -135,7 +135,7 @@ pub(crate) struct PreparedQueries { /// CIP36 Registration errors. cip36_registration_error_insert_queries: SizedBatch, /// CIP36 Registration for Stake Address Insert query. - cip36_registration_for_stake_address_insert_queries: SizedBatch, + cip36_registration_for_vote_key_insert_queries: SizedBatch, /// Update TXO spent query. txo_spent_update_queries: SizedBatch, /// Get TXO by stake address query. @@ -228,7 +228,7 @@ impl PreparedQueries { let ( cip36_registration_insert_queries, cip36_registration_error_insert_queries, - cip36_registration_for_stake_address_insert_queries, + cip36_registration_for_vote_key_insert_queries, ) = all_cip36_queries?; let ( @@ -247,7 +247,7 @@ impl PreparedQueries { stake_registration_insert_queries, cip36_registration_insert_queries, cip36_registration_error_insert_queries, - cip36_registration_for_stake_address_insert_queries, + cip36_registration_for_vote_key_insert_queries, txo_spent_update_queries, txo_by_stake_address_query: txo_by_stake_address_query?, txi_by_txn_hash_query: txi_by_txn_hash_query?, @@ -396,8 +396,8 @@ impl PreparedQueries { PreparedQuery::Cip36RegistrationInsertErrorQuery => { &self.cip36_registration_error_insert_queries }, - PreparedQuery::Cip36RegistrationForStakeAddrInsertQuery => { - &self.cip36_registration_for_stake_address_insert_queries + PreparedQuery::Cip36RegistrationForVoteKeyInsertQuery => { + &self.cip36_registration_for_vote_key_insert_queries }, PreparedQuery::TxoSpentUpdateQuery => &self.txo_spent_update_queries, PreparedQuery::Rbac509InsertQuery => &self.rbac509_registration_insert_queries, diff --git a/catalyst-gateway/bin/src/db/index/schema/cql/stake_registration.cql b/catalyst-gateway/bin/src/db/index/schema/cql/stake_registration.cql index a84419f078e..596a61cd7b2 100644 --- a/catalyst-gateway/bin/src/db/index/schema/cql/stake_registration.cql +++ b/catalyst-gateway/bin/src/db/index/schema/cql/stake_registration.cql @@ -11,8 +11,9 @@ CREATE TABLE IF NOT EXISTS stake_registration ( -- Stake key lifecycle data, shows what happened with the stake key at this slot#. script boolean, -- Is the address a script address. - register boolean, -- True if the stake was registered in this transaction. - deregister boolean, -- True if the stake key was deregistered in this transaction. + register boolean, -- True if the this transaction contains cardano stake registration cert. + deregister boolean, -- True if the this transaction contains cardano stake deregistration cert. + cip36 boolean, -- True if the this transaction contains CIP36 registration. pool_delegation blob, -- Stake was delegated to this Pool address. -- Not present if delegation did not change. diff --git a/catalyst-gateway/bin/src/db/index/schema/mod.rs b/catalyst-gateway/bin/src/db/index/schema/mod.rs index 314e3406480..3aa8d1594f8 100644 --- a/catalyst-gateway/bin/src/db/index/schema/mod.rs +++ b/catalyst-gateway/bin/src/db/index/schema/mod.rs @@ -264,7 +264,7 @@ mod tests { /// This constant is ONLY used by Unit tests to identify when the schema version will /// change accidentally, and is NOT to be used directly to set the schema version of /// the table namespaces. - const SCHEMA_VERSION: &str = "772902fc-d5ec-871e-aaca-5b26c96a8cf6"; + const SCHEMA_VERSION: &str = "f2e9ee5e-278c-8c9c-b3f6-8a27d4443e29"; #[test] /// This test is designed to fail if the schema version has changed. diff --git a/catalyst-gateway/bin/src/db/index/tests/scylla_purge.rs b/catalyst-gateway/bin/src/db/index/tests/scylla_purge.rs index d79c3486479..df41225bc34 100644 --- a/catalyst-gateway/bin/src/db/index/tests/scylla_purge.rs +++ b/catalyst-gateway/bin/src/db/index/tests/scylla_purge.rs @@ -340,10 +340,7 @@ async fn test_cip36_registration_for_vote_key() { // insert session - .execute_batch( - PreparedQuery::Cip36RegistrationForStakeAddrInsertQuery, - data, - ) + .execute_batch(PreparedQuery::Cip36RegistrationForVoteKeyInsertQuery, data) .await .unwrap(); @@ -545,7 +542,8 @@ async fn test_stake_registration() { stake_address_1(), 0.into(), 0.into(), - Some(stake_public_key_1), + stake_public_key_1, + false, false, false, false, @@ -555,7 +553,8 @@ async fn test_stake_registration() { stake_address_2(), 1.into(), 1.into(), - Some(stake_public_key_2), + stake_public_key_2, + true, true, true, true, diff --git a/catalyst-gateway/bin/src/service/api/documents/templates/mod.rs b/catalyst-gateway/bin/src/service/api/documents/templates/mod.rs index d7e28508118..dea2f8820e5 100644 --- a/catalyst-gateway/bin/src/service/api/documents/templates/mod.rs +++ b/catalyst-gateway/bin/src/service/api/documents/templates/mod.rs @@ -93,7 +93,6 @@ fn build_signed_doc(data: &SignedDocData, sk: &SigningKey) -> (Uuid, CatalystSig const KID_NETWORK: &str = "cardano"; let metadata = serde_json::json!({ - "alg": catalyst_signed_doc::Algorithm::EdDSA.to_string(), "type": data.doc_type, "id": data.id, "ver": data.ver, diff --git a/catalyst-gateway/bin/src/service/common/auth/rbac/scheme.rs b/catalyst-gateway/bin/src/service/common/auth/rbac/scheme.rs index b1411e5b2cf..7c6c5a90f96 100644 --- a/catalyst-gateway/bin/src/service/common/auth/rbac/scheme.rs +++ b/catalyst-gateway/bin/src/service/common/auth/rbac/scheme.rs @@ -240,12 +240,11 @@ async fn last_signing_key( .data() .signing_key() .context("Missing signing key")?; - let key_offset = usize::try_from(key_ref.key_offset).context("Invalid signing key offset")?; match key_ref.local_ref { LocalRefInt::X509Certs => { let cert = &chain .x509_certs() - .get(&key_offset) + .get(&key_ref.key_offset) .context("Missing X509 role 0 certificate")? .last() .and_then(|p| p.data().as_ref()) @@ -255,7 +254,7 @@ async fn last_signing_key( LocalRefInt::C509Certs => { let cert = &chain .c509_certs() - .get(&key_offset) + .get(&key_ref.key_offset) .context("Missing C509 role 0 certificate")? .last() .and_then(|p| p.data().as_ref()) From b1e75425108b3e7bc5bc00156f2b6f48c95d0f06 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Sun, 30 Mar 2025 23:22:43 +0300 Subject: [PATCH 02/10] wip --- .../src/service/api/cardano/cip36/endpoint.rs | 3 +- .../src/service/api/cardano/cip36/filter.rs | 2 +- .../bin/src/service/api/cardano/cip36/mod.rs | 11 +- .../src/service/api/cardano/cip36/response.rs | 145 ++++++++---------- ...19_shelley_address.rs => cip19_address.rs} | 41 ++--- .../types/cardano/cip19_stake_address.rs | 21 ++- .../src/service/common/types/cardano/mod.rs | 2 +- .../service/common/types/generic/boolean.rs | 14 ++ .../src/service/common/types/generic/mod.rs | 1 + 9 files changed, 126 insertions(+), 114 deletions(-) rename catalyst-gateway/bin/src/service/common/types/cardano/{cip19_shelley_address.rs => cip19_address.rs} (79%) create mode 100644 catalyst-gateway/bin/src/service/common/types/generic/boolean.rs diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs index 00ddb11f6d3..99ea4160402 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs @@ -1,6 +1,5 @@ //! Implementation of the GET `/cardano/cip36` endpoint -use poem::http::HeaderMap; use tracing::error; use self::cardano::query::stake_or_voter::StakeAddressOrPublicKey; @@ -21,7 +20,7 @@ use crate::{ pub(crate) async fn cip36_registrations( lookup: Option, asat: Option, _page: common::types::generic::query::pagination::Page, - _limit: common::types::generic::query::pagination::Limit, _headers: &HeaderMap, + _limit: common::types::generic::query::pagination::Limit, _invalid: bool, ) -> AllRegistration { let Some(session) = CassandraSession::get(true) else { error!("Failed to acquire db session"); diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs index 9ddf3fe8f6a..73facb93b46 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs @@ -9,7 +9,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use tracing::error; use super::{ - cardano::{cip19_shelley_address::Cip19ShelleyAddress, nonce::Nonce}, + cardano::{cip19_address::Cip19ShelleyAddress, nonce::Nonce}, common::types::generic::error_msg::ErrorMessage, response::{ AllRegistration, Cip36Details, Cip36Registration, Cip36RegistrationList, diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/mod.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/mod.rs index db19e945b77..b9e9f77aa8f 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/mod.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/mod.rs @@ -9,7 +9,10 @@ use super::Ed25519HexEncodedPublicKey; use crate::service::common::{ self, tags::ApiTags, - types::cardano::{self}, + types::{ + cardano::{self}, + generic::boolean::BooleanFlag, + }, }; pub(crate) mod endpoint; @@ -43,11 +46,15 @@ impl Api { method = "get", operation_id = "cardanoRegistrationCip36" )] + #[allow(clippy::too_many_arguments)] async fn get_registration( &self, lookup: Query>, asat: Query>, page: Query>, limit: Query>, + /// Flag for returning invalid registrations, if not provided or set to false, + /// returns only valid registrations + Query(invalid): Query>, /// Headers, used if the query is requesting ALL to determine if the secret API /// Key is also defined. headers: &HeaderMap, @@ -72,7 +79,7 @@ impl Api { SlotNo::into_option(asat.0), page.0.unwrap_or_default(), limit.0.unwrap_or_default(), - headers, + invalid.is_some_and(Into::into), ) .await } diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/response.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/response.rs index 2ff39340ddb..610498e63ce 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/response.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/response.rs @@ -4,11 +4,22 @@ use catalyst_types::problem_report::ProblemReport; use derive_more::{From, Into}; use poem_openapi::{ payload::Json, + registry::MetaSchema, types::{Example, ToJSON}, ApiResponse, NewType, Object, }; -use crate::service::{common, common::types::array_types::impl_array_types}; +use crate::service::common::{ + self, + objects::generic::json_object::JSONObject, + types::{ + array_types::impl_array_types, + cardano::{ + cip19_address::Cip19Address, nonce::Nonce, slot_no::SlotNo, txn_index::TxnIndex, + }, + generic::{boolean::BooleanFlag, ed25519_public_key::Ed25519HexEncodedPublicKey}, + }, +}; // ToDo: The examples of this response should be taken from representative data from a // response generated on pre-prod. @@ -34,18 +45,18 @@ pub(crate) type AllRegistration = common::responses::WithErrorResponses, + /// Flag which identifies that resulted registrations are all valid or not + pub is_valid: BooleanFlag, + /// List of registrations that were found, for the requested filter from the finalized + /// blockchain state. #[oai(skip_serializing_if_is_empty)] - pub invalid: common::types::cardano::registration_list::RegistrationCip36List, + pub regs: Cip36List, /// Current Page #[oai(skip_serializing_if_is_none)] pub page: Option, @@ -54,11 +65,9 @@ pub(crate) struct Cip36RegistrationList { impl Example for Cip36RegistrationList { fn example() -> Self { Self { - slot: (common::types::cardano::slot_no::EXAMPLE + 635) - .try_into() - .unwrap_or_default(), - voting_key: Example::example(), - invalid: vec![Cip36Details::invalid_example()].into(), + slot: Some(SlotNo::example()), + is_valid: true.into(), + regs: vec![Cip36Details::example()].into(), page: Some(Example::example()), } } @@ -80,78 +89,53 @@ impl Example for Cip36RegistrationListPage { } } -// List of CIP-36 Registrations for voting public key -impl_array_types!( - Cip36RegistrationsForVotingPublicKeyList, - Cip36RegistrationsForVotingPublicKey, - Some(poem_openapi::registry::MetaSchema { - example: Self::example().to_json(), - max_items: Some(100), - items: Some(Box::new(Cip36RegistrationsForVotingPublicKey::schema_ref())), - ..poem_openapi::registry::MetaSchema::ANY - }) -); - -impl Example for Cip36RegistrationsForVotingPublicKeyList { - fn example() -> Self { - Self(vec![Example::example()]) - } -} - -/// List of CIP36 Registration Data for a Voting Key. -#[derive(Object, Debug, Clone)] -#[oai(example = true)] -pub(crate) struct Cip36RegistrationsForVotingPublicKey { - /// Voting Public Key - pub vote_pub_key: common::types::generic::ed25519_public_key::Ed25519HexEncodedPublicKey, - /// List of Registrations associated with this Voting Key - pub registrations: common::types::cardano::registration_list::RegistrationCip36List, -} - -impl Example for Cip36RegistrationsForVotingPublicKey { - fn example() -> Self { - Self { - vote_pub_key: Example::example(), - registrations: Example::example(), - } - } -} - /// CIP36 Registration Data as found on-chain. #[derive(Object, Debug, Clone)] #[oai(example = true)] pub(crate) struct Cip36Details { /// Blocks Slot Number that the registration certificate is in. - pub slot_no: common::types::cardano::slot_no::SlotNo, + pub slot_no: SlotNo, /// Full Stake Address (not hashed, 32 byte ED25519 Public key). #[oai(skip_serializing_if_is_none)] - pub stake_pub_key: - Option, + pub stake_pub_key: Option, /// Voting Public Key (Ed25519 Public key). #[oai(skip_serializing_if_is_none)] - pub vote_pub_key: - Option, - #[allow(clippy::missing_docs_in_private_items)] // Type is pre documented. + pub vote_pub_key: Option, + /// A Catalyst corrected nonce, which is used during sorting of registrations. #[oai(skip_serializing_if_is_none)] - pub nonce: Option, + pub nonce: Option, + /// Raw nonce (nonce that has not had slot correction applied). + /// Field 4 in the CIP-36 61284 Spec. + #[oai(skip_serializing_if_is_none)] + pub raw_nonce: Option, #[allow(clippy::missing_docs_in_private_items)] // Type is pre documented. #[oai(skip_serializing_if_is_none)] - pub txn: Option, - /// Cardano Cip-19 Formatted Shelley Payment Address. + pub txn_index: Option, + /// Cardano Cip-19 Formatted Address. #[oai(skip_serializing_if_is_none)] - pub payment_address: Option, + pub payment_address: Option, /// If the payment address is a script, then it can not be payed rewards. - #[oai(default)] - pub is_payable: common::types::cardano::boolean::IsPayable, + pub is_payable: BooleanFlag, /// If this field is set, then the registration was in CIP15 format. - #[oai(default)] - pub cip15: common::types::cardano::boolean::IsCip15, + pub cip15: BooleanFlag, /// If there are errors with this registration, they are listed here. /// This field is *NEVER* returned for a valid registration. #[oai(skip_serializing_if_is_none)] - pub errors: Option, + pub report: Option, } +// List of CIP-36 Registrations +impl_array_types!( + Cip36List, + Cip36Details, + Some(MetaSchema { + example: Self::example().to_json(), + max_items: Some(100), + items: Some(Box::new(Cip36Details::schema_ref())), + ..poem_openapi::registry::MetaSchema::ANY + }) +); + impl Example for Cip36Details { /// Example of a valid registration fn example() -> Self { @@ -164,23 +148,23 @@ impl Example for Cip36Details { common::types::generic::ed25519_public_key::Ed25519HexEncodedPublicKey::example(), ), nonce: Some(common::types::cardano::nonce::Nonce::example()), - txn: Some(common::types::cardano::txn_index::TxnIndex::example()), - payment_address: Some( - common::types::cardano::cip19_shelley_address::Cip19ShelleyAddress::example(), - ), + raw_nonce: Some(common::types::cardano::nonce::Nonce::example()), + txn_index: Some(common::types::cardano::txn_index::TxnIndex::example()), + payment_address: Some(common::types::cardano::cip19_address::Cip19Address::example()), is_payable: true.into(), cip15: false.into(), - errors: None, + report: None, } } } impl Cip36Details { /// Example of an invalid registration - fn invalid_example() -> Self { + #[allow(dead_code)] + pub(crate) fn invalid_example() -> Self { let problem_report = ProblemReport::new("Cip36"); problem_report.other("Error occurred", "Cip36 decoding error"); - let errors = serde_json::to_string(&problem_report).unwrap_or_default(); + let json_report = serde_json::to_value(&problem_report).unwrap_or_default(); Self { slot_no: (common::types::cardano::slot_no::EXAMPLE + 135) @@ -191,17 +175,22 @@ impl Cip36Details { common::types::generic::ed25519_public_key::Ed25519HexEncodedPublicKey::example(), ), nonce: Some((common::types::cardano::nonce::EXAMPLE + 97).into()), - txn: Some(common::types::cardano::txn_index::TxnIndex::example()), + raw_nonce: Some((common::types::cardano::nonce::EXAMPLE + 97).into()), + txn_index: Some(common::types::cardano::txn_index::TxnIndex::example()), payment_address: None, is_payable: false.into(), cip15: true.into(), - errors: Some( - crate::service::common::types::generic::error_msg::ErrorMessage::from(errors), - ), + report: Some(json_report.into()), } } } +impl Example for Cip36List { + fn example() -> Self { + Self(vec![Example::example()]) + } +} + /// Cip36 Registration Validation Error. #[derive(Object)] #[oai(example = true)] diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs b/catalyst-gateway/bin/src/service/common/types/cardano/cip19_address.rs similarity index 79% rename from catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs rename to catalyst-gateway/bin/src/service/common/types/cardano/cip19_address.rs index 6be00532a9d..b75949677b0 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/cip19_address.rs @@ -9,7 +9,7 @@ use std::{ }; use const_format::concatcp; -use pallas::ledger::addresses::{Address, ShelleyAddress}; +use pallas::ledger::addresses::Address; use poem_openapi::{ registry::{MetaExternalDocument, MetaSchema, MetaSchemaRef}, types::{Example, ParseError, ParseFromJSON, ParseFromParameter, ParseResult, ToJSON, Type}, @@ -19,9 +19,9 @@ use serde_json::Value; use crate::service::common::types::string_types::impl_string_types; /// Title -const TITLE: &str = "Cardano Payment Address"; +const TITLE: &str = "Cardano CIP-19 Address"; /// Description -const DESCRIPTION: &str = "Cardano Shelley Payment Address (CIP-19 Formatted)."; +const DESCRIPTION: &str = "Cardano CIP-19 Formatted Address."; /// Example // cSpell:disable const EXAMPLE: &str = "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae"; @@ -30,11 +30,16 @@ const EXAMPLE: &str = "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3 const PROD: &str = "addr"; /// Test Address Identifier const TEST: &str = "addr_test"; +/// Production Stake Address Identifier +pub(crate) const STAKE_PROD: &str = "stake"; +/// Test Stake Address Identifier +pub(crate) const STAKE_TEST: &str = "stake_test"; /// Bech32 Match Pattern const BECH32: &str = "[a,c-h,j-n,p-z,0,2-9]"; -/// Length of the encoded address (for type 0 - 3). +/// Length of the encoded address that have 2 parts (for Shelley address type 0 - 3). const ENCODED_STAKED_ADDR_LEN: usize = 98; -/// Length of the encoded address (for type 6 - 7). +/// Length of the encoded address that have 1 part (for Shelley address type 6 - 7, and +/// stake address). const ENCODED_UNSTAKED_ADDR_LEN: usize = 53; /// Regex Pattern const PATTERN: &str = concatcp!( @@ -42,6 +47,10 @@ const PATTERN: &str = concatcp!( PROD, "|", TEST, + "|", + STAKE_PROD, + "|", + STAKE_TEST, ")1(", BECH32, "{", @@ -98,29 +107,26 @@ fn is_valid(addr: &str) -> bool { } impl_string_types!( - Cip19ShelleyAddress, + Cip19Address, "string", "cardano:cip19-address", Some(SCHEMA.clone()), is_valid ); -impl TryFrom> for Cip19ShelleyAddress { +impl TryFrom> for Cip19Address { type Error = anyhow::Error; fn try_from(bytes: Vec) -> Result { let addr = Address::from_bytes(&bytes)?; - let Address::Shelley(addr) = addr else { - return Err(anyhow::anyhow!("Not a Shelley address: {addr}")); - }; addr.try_into() } } -impl TryFrom for Cip19ShelleyAddress { +impl TryFrom
for Cip19Address { type Error = anyhow::Error; - fn try_from(addr: ShelleyAddress) -> Result { + fn try_from(addr: Address) -> Result { let addr_str = addr .to_bech32() .map_err(|e| anyhow::anyhow!(format!("Invalid payment address {e}")))?; @@ -128,20 +134,17 @@ impl TryFrom for Cip19ShelleyAddress { } } -impl TryInto for Cip19ShelleyAddress { +impl TryInto
for Cip19Address { type Error = anyhow::Error; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let address_str = &self.0; let address = Address::from_bech32(address_str)?; - match address { - Address::Shelley(address) => Ok(address), - _ => Err(anyhow::anyhow!("Invalid payment address")), - } + Ok(address) } } -impl Example for Cip19ShelleyAddress { +impl Example for Cip19Address { fn example() -> Self { Self(EXAMPLE.to_owned()) } diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/cip19_stake_address.rs b/catalyst-gateway/bin/src/service/common/types/cardano/cip19_stake_address.rs index 26eb66447c5..990573b939d 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/cip19_stake_address.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/cip19_stake_address.rs @@ -18,7 +18,10 @@ use poem_openapi::{ }; use serde_json::Value; -use crate::service::common::types::string_types::impl_string_types; +use crate::service::common::types::{ + cardano::cip19_address::{STAKE_PROD, STAKE_TEST}, + string_types::impl_string_types, +}; /// Stake address title. const TITLE: &str = "Cardano stake address"; @@ -28,16 +31,12 @@ const DESCRIPTION: &str = "Cardano stake address, also known as a reward address // cSpell:disable pub(crate) const EXAMPLE: &str = "stake_test1uqehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gssrtvn"; // cSpell:enable -/// Production Stake Address Identifier -const PROD_STAKE: &str = "stake"; -/// Test Stake Address Identifier -const TEST_STAKE: &str = "stake_test"; /// Regex Pattern pub(crate) const PATTERN: &str = concatcp!( "(", - PROD_STAKE, + STAKE_PROD, "|", - TEST_STAKE, + STAKE_TEST, ")1[a,c-h,j-n,p-z,0,2-9]{53}" ); /// Length of the encoded address. @@ -45,9 +44,9 @@ const ENCODED_ADDR_LEN: usize = 53; /// Length of the decoded address. const DECODED_ADDR_LEN: usize = 29; /// Minimum length -pub(crate) const MIN_LENGTH: usize = PROD_STAKE.len() + 1 + ENCODED_ADDR_LEN; +pub(crate) const MIN_LENGTH: usize = STAKE_PROD.len() + 1 + ENCODED_ADDR_LEN; /// Minimum length -pub(crate) const MAX_LENGTH: usize = TEST_STAKE.len() + 1 + ENCODED_ADDR_LEN; +pub(crate) const MAX_LENGTH: usize = STAKE_TEST.len() + 1 + ENCODED_ADDR_LEN; /// String Format pub(crate) const FORMAT: &str = "cardano:cip19-address"; @@ -81,7 +80,7 @@ fn is_valid(stake_addr: &str) -> bool { // Just check the string can be safely converted into the type. if let Ok((hrp, addr)) = bech32::decode(stake_addr) { let hrp = hrp.as_str(); - addr.len() == DECODED_ADDR_LEN && (hrp == PROD_STAKE || hrp == TEST_STAKE) + addr.len() == DECODED_ADDR_LEN && (hrp == STAKE_PROD || hrp == STAKE_TEST) } else { false } @@ -110,7 +109,7 @@ impl TryFrom for Cip19StakeAddress { match bech32::decode(&value) { Ok((hrp, addr)) => { let hrp = hrp.as_str(); - if addr.len() == DECODED_ADDR_LEN && (hrp == PROD_STAKE || hrp == TEST_STAKE) { + if addr.len() == DECODED_ADDR_LEN && (hrp == STAKE_PROD || hrp == STAKE_TEST) { return Ok(Cip19StakeAddress(value)); } bail!("Invalid CIP-19 formatted Stake Address") diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs b/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs index e1f92a6cd30..f2c14a25264 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod asset_name; pub(crate) mod asset_value; pub(crate) mod boolean; pub(crate) mod catalyst_id; -pub(crate) mod cip19_shelley_address; +pub(crate) mod cip19_address; pub(crate) mod cip19_stake_address; pub(crate) mod hash28; pub(crate) mod nonce; diff --git a/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs b/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs new file mode 100644 index 00000000000..9596c75584b --- /dev/null +++ b/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs @@ -0,0 +1,14 @@ +//! Implement type wrapper for boolean type +use derive_more::{From, Into}; +use poem_openapi::{types::Example, NewType}; + +/// Boolean flag +#[derive(NewType, Debug, Clone, From, Into)] +#[oai(example = true)] +pub(crate) struct BooleanFlag(bool); + +impl Example for BooleanFlag { + fn example() -> Self { + Self(true) + } +} \ No newline at end of file diff --git a/catalyst-gateway/bin/src/service/common/types/generic/mod.rs b/catalyst-gateway/bin/src/service/common/types/generic/mod.rs index 87968431fe3..ecf504483c5 100644 --- a/catalyst-gateway/bin/src/service/common/types/generic/mod.rs +++ b/catalyst-gateway/bin/src/service/common/types/generic/mod.rs @@ -2,6 +2,7 @@ //! //! These types may be used in Cardano, but are not specific to Cardano. +pub(crate) mod boolean; pub(crate) mod date_time; pub(crate) mod ed25519_public_key; pub(crate) mod error_list; From d12af60afc79e12b3a8d0af7b5f4184231180772 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Sun, 30 Mar 2025 23:33:45 +0300 Subject: [PATCH 03/10] wip --- .../bin/src/db/index/queries/mod.rs | 7 +- .../registrations/get_from_vote_key.rs | 22 +- .../bin/src/db/index/tests/scylla_queries.rs | 2 +- .../src/service/api/cardano/cip36/filter.rs | 908 +++++++----------- .../src/service/common/types/cardano/mod.rs | 1 - .../common/types/cardano/registration_list.rs | 28 - .../service/common/types/generic/boolean.rs | 2 +- 7 files changed, 369 insertions(+), 601 deletions(-) delete mode 100644 catalyst-gateway/bin/src/service/common/types/cardano/registration_list.rs diff --git a/catalyst-gateway/bin/src/db/index/queries/mod.rs b/catalyst-gateway/bin/src/db/index/queries/mod.rs index f2de302702a..cb49f6d241a 100644 --- a/catalyst-gateway/bin/src/db/index/queries/mod.rs +++ b/catalyst-gateway/bin/src/db/index/queries/mod.rs @@ -15,8 +15,8 @@ use crossbeam_skiplist::SkipMap; use registrations::{ get_all_invalids::GetAllInvalidRegistrationsQuery, get_all_registrations::GetAllRegistrationsQuery, get_from_stake_addr::GetRegistrationQuery, - get_from_stake_address::GetStakeAddrQuery, get_from_vote_key::GetStakeAddrFromVoteKeyQuery, - get_invalid::GetInvalidRegistrationQuery, + get_from_stake_address::GetStakeAddrQuery, + get_from_vote_key::GetStakePublicKeyFromVoteKeyQuery, get_invalid::GetInvalidRegistrationQuery, }; use scylla::{ batch::Batch, prepared_statement::PreparedStatement, serialize::row::SerializeRow, @@ -203,7 +203,8 @@ impl PreparedQueries { let registration_from_stake_addr_query = GetRegistrationQuery::prepare(session.clone()).await; let stake_addr_from_stake_address = GetStakeAddrQuery::prepare(session.clone()).await; - let stake_addr_from_vote_key = GetStakeAddrFromVoteKeyQuery::prepare(session.clone()).await; + let stake_addr_from_vote_key = + GetStakePublicKeyFromVoteKeyQuery::prepare(session.clone()).await; let invalid_registrations = GetInvalidRegistrationQuery::prepare(session.clone()).await; let get_all_registrations_query = GetAllRegistrationsQuery::prepare(session.clone()).await; let get_all_invalid_registrations_query = diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_vote_key.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_vote_key.rs index b67a2ed20fa..501e3109106 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_vote_key.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_vote_key.rs @@ -15,28 +15,28 @@ use crate::db::index::{ /// Get stake addr from vote key query. const GET_STAKE_ADDR_FROM_VOTE_KEY: &str = include_str!("../cql/get_stake_addr_w_vote_key.cql"); -/// Get stake addr +/// Get stake public key #[derive(SerializeRow)] -pub(crate) struct GetStakeAddrFromVoteKeyParams { +pub(crate) struct GetStakePublicKeyFromVoteKeyParams { /// Vote key. pub vote_key: Vec, } -impl GetStakeAddrFromVoteKeyParams { - /// Create a new instance of [`GetStakeAddrFromVoteKeyParams`] - pub(crate) fn new(vote_key: Vec) -> GetStakeAddrFromVoteKeyParams { +impl GetStakePublicKeyFromVoteKeyParams { + /// Create a new instance of [`GetStakePublicKeyFromVoteKeyParams`] + pub(crate) fn new(vote_key: Vec) -> GetStakePublicKeyFromVoteKeyParams { Self { vote_key } } } /// Get stake addr from vote key query. #[derive(DeserializeRow)] -pub(crate) struct GetStakeAddrFromVoteKeyQuery { +pub(crate) struct GetStakePublicKeyFromVoteKeyQuery { /// Full Stake Address (not hashed, 32 byte ED25519 Public key). pub stake_public_key: Vec, } -impl GetStakeAddrFromVoteKeyQuery { +impl GetStakePublicKeyFromVoteKeyQuery { /// Prepares a get stake addr from vote key query. pub(crate) async fn prepare(session: Arc) -> anyhow::Result { PreparedQueries::prepare( @@ -47,19 +47,19 @@ impl GetStakeAddrFromVoteKeyQuery { ) .await .inspect_err( - |error| error!(error=%error, "Failed to prepare get stake addr from vote key query."), + |error| error!(error=%error, "Failed to prepare get stake public key from vote key query."), ) .map_err(|error| anyhow::anyhow!("{error}\n--\n{GET_STAKE_ADDR_FROM_VOTE_KEY}")) } /// Executes a get txi by transaction hashes query. pub(crate) async fn execute( - session: &CassandraSession, params: GetStakeAddrFromVoteKeyParams, - ) -> anyhow::Result> { + session: &CassandraSession, params: GetStakePublicKeyFromVoteKeyParams, + ) -> anyhow::Result> { let iter = session .execute_iter(PreparedSelectQuery::StakeAddrFromVoteKey, params) .await? - .rows_stream::()?; + .rows_stream::()?; Ok(iter) } diff --git a/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs b/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs index dca92af240b..b973c1d87f3 100644 --- a/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs +++ b/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs @@ -194,7 +194,7 @@ async fn test_get_stake_addr_w_vote_key() { }; let mut row_stream = - GetStakeAddrFromVoteKeyQuery::execute(&session, GetStakeAddrFromVoteKeyParams { + GetStakePublicKeyFromVoteKeyQuery::execute(&session, GetStakePublicKeyFromVoteKeyParams { vote_key: vec![], }) .await diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs index 73facb93b46..7d1c6625e96 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs @@ -1,495 +1,265 @@ //! Implementation of the GET `/cardano/cip36` endpoint -use std::{cmp::Reverse, sync::Arc}; +use std::sync::Arc; use cardano_blockchain_types::StakeAddress; -use dashmap::DashMap; -use futures::{future, StreamExt}; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use tracing::error; +use futures::TryStreamExt; use super::{ - cardano::{cip19_address::Cip19ShelleyAddress, nonce::Nonce}, - common::types::generic::error_msg::ErrorMessage, - response::{ - AllRegistration, Cip36Details, Cip36Registration, Cip36RegistrationList, - Cip36RegistrationsForVotingPublicKey, - }, + cardano::cip19_address::Cip19Address, + response::{Cip36Details, Cip36Registration, Cip36RegistrationList}, Ed25519HexEncodedPublicKey, SlotNo, }; -use crate::db::index::{ - queries::registrations::{ - get_all_invalids::{GetAllInvalidRegistrationsParams, GetAllInvalidRegistrationsQuery}, - get_all_registrations::{GetAllRegistrationsParams, GetAllRegistrationsQuery}, - get_from_stake_addr::{GetRegistrationParams, GetRegistrationQuery}, - get_from_stake_address::{GetStakeAddrParams, GetStakeAddrQuery}, - get_from_vote_key::{GetStakeAddrFromVoteKeyParams, GetStakeAddrFromVoteKeyQuery}, - get_invalid::{GetInvalidRegistrationParams, GetInvalidRegistrationQuery}, - }, - session::CassandraSession, -}; - -/// Get registration given a stake key hash, it can be time specific based on asat param, -/// or the latest registration returned if no asat given. -pub(crate) async fn get_registration_given_stake_key_hash( - stake_address: StakeAddress, session: Arc, asat: Option, -) -> AllRegistration { - // Get stake addr associated with given stake hash. - let mut stake_addr_iter = - match GetStakeAddrQuery::execute(&session, GetStakeAddrParams::new(stake_address)).await { - Ok(stake_addr) => stake_addr, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Failed to query stake addr from stake hash {err:?}", - )); - }, - }; - - if let Some(row_stake_addr) = stake_addr_iter.next().await { - let row = match row_stake_addr { - Ok(r) => r, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Failed to query stake addr from stake hash {err:?}", - )); - }, - }; - - // Stake hash successfully converted into associated stake pub key which we use to lookup - // registrations. - let stake_pub_key = match Ed25519HexEncodedPublicKey::try_from(row.stake_public_key.clone()) - { - Ok(key) => key, - Err(err) => { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Failed to type stake address {err:?}", - )); - }, - }; - - return get_registration_from_stake_addr(stake_pub_key, asat, session, None).await; - } - - AllRegistration::With(Cip36Registration::NotFound) -} - -/// Get registration from stake addr -pub async fn get_registration_from_stake_addr( - stake_pub_key: Ed25519HexEncodedPublicKey, asat: Option, - session: Arc, vote_key: Option, -) -> AllRegistration { - // Get all registrations from given stake pub key. - let mut registrations = - match get_all_registrations_from_stake_pub_key(&session.clone(), stake_pub_key.clone()) - .await - { - Ok(registration) => registration, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Failed to query stake stake pub key {err:?}", - )); - }, - }; - - // check registrations are still actively associated with the voting key, - // and have not been registered to another voting key. - if let Some(vote_key) = vote_key { - registrations = check_stake_addr_voting_key_association(registrations, &vote_key); - } - - // Query requires the registration to be bound by time. - let registration = if let Some(slot_no) = asat { - match get_registration_given_slot_no(registrations, slot_no) { - Ok(registration) => registration, - Err(err) => { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Failed to get registration given slot no {err:?}", - )); +use crate::{ + db::index::{ + queries::registrations::{ + get_all_invalids::{GetAllInvalidRegistrationsParams, GetAllInvalidRegistrationsQuery}, + get_all_registrations::{GetAllRegistrationsParams, GetAllRegistrationsQuery}, + get_from_stake_pk::{GetRegistrationParams, GetRegistrationQuery}, + get_from_vote_key::{ + GetStakePublicKeyFromVoteKeyParams, GetStakePublicKeyFromVoteKeyQuery, }, - } - } else { - // Query not bound by time, return latest registration. - match sort_latest_registration(registrations) { - Ok(registration) => registration, - Err(err) => { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Failed to sort latest registration {err:?}", - )); + get_invalid::{GetInvalidRegistrationParams, GetInvalidRegistrationQuery}, + get_stake_pk_from_stake_addr::{ + GetStakePublicKeyFromStakeAddrParams, GetStakePublicKeyFromStakeAddrQuery, }, - } - }; - - // Registration found, now find invalids. - let slot_no = registration.clone().slot_no; - let Some(stake_pub_key) = registration.clone().stake_pub_key else { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Stake pub key not in registration {stake_pub_key:?}", - )); - }; - - let Some(vote_pub_key) = registration.clone().vote_pub_key else { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Vote pub key not in registration {stake_pub_key:?}", - )); - }; - - // include any erroneous registrations which occur AFTER the slot# of the last valid - // registration - let invalids_report = - match get_invalid_registrations(stake_pub_key, Some(slot_no), session).await { - Ok(invalids) => invalids, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Failed to obtain invalid registrations for given stake pub key {err:?}", - )); - }, - }; - - AllRegistration::With(Cip36Registration::Ok(poem_openapi::payload::Json( - Cip36RegistrationList { - slot: slot_no, - voting_key: vec![Cip36RegistrationsForVotingPublicKey { - vote_pub_key, - registrations: vec![registration.clone()].into(), - }] - .into(), - invalid: invalids_report.into(), - page: None, }, - ))) -} - -/// Stake addresses need to be individually checked to make sure they are still actively -/// associated with the voting key, and have not been registered to another voting key. -fn check_stake_addr_voting_key_association( - registrations: Vec, associated_voting_key: &Ed25519HexEncodedPublicKey, -) -> Vec { - registrations - .into_par_iter() - .filter(|registration| cross_reference_key(associated_voting_key, registration)) - .collect() -} - -/// Check associated voting key matches registration voting key -fn cross_reference_key( - associated_voting_key: &Ed25519HexEncodedPublicKey, r: &Cip36Details, -) -> bool { - r.vote_pub_key - .clone() - .map(|key| key == *associated_voting_key) - .is_some() -} - -/// Get all cip36 registrations for a given stake address. -async fn get_all_registrations_from_stake_pub_key( - session: &Arc, stake_pub_key: Ed25519HexEncodedPublicKey, -) -> Result, anyhow::Error> { - let mut registrations_iter = GetRegistrationQuery::execute(session, GetRegistrationParams { - stake_public_key: stake_pub_key.clone().try_into()?, - }) - .await?; - let mut registrations = Vec::new(); - while let Some(row) = registrations_iter.next().await { - let row = row?; - - let nonce = if let Some(nonce) = row.nonce.into_parts().1.to_u64_digits().first() { - *nonce - } else { - continue; - }; - - let slot_no: u64 = row.slot_no.into(); - - let slot_no = match SlotNo::try_from(slot_no) { - Ok(slot_no) => slot_no, - Err(err) => { - error!("Corrupt valid registration {:?}", err); - // This should NOT happen, valid registrations should be infallible. - // If it happens, there is an indexing issue. - continue; - }, - }; - - let payment_address = match Cip19ShelleyAddress::try_from(row.payment_address) { - Ok(payment_addr) => Some(payment_addr), - Err(err) => { - // This should NOT happen, valid registrations should be infallible. - // If it happens, there is an indexing issue. - error!( - "Corrupt valid registration {:?}\n Stake pub key:{:?}", - err, stake_pub_key - ); - continue; - }, - }; - - let vote_pub_key = match Ed25519HexEncodedPublicKey::try_from(row.vote_key) { - Ok(vote_pub_key) => Some(vote_pub_key), - Err(err) => { - error!( - "Corrupt valid registration {:?}\n Stake pub key:{:?}", - err, stake_pub_key - ); - continue; - }, - }; - - let cip36 = Cip36Details { - slot_no, - stake_pub_key: Some(stake_pub_key.clone()), - vote_pub_key, - nonce: Some(Nonce::from(nonce)), - txn: Some(row.txn_index.into()), - payment_address, - is_payable: row.is_payable.into(), - cip15: (!row.cip36).into(), - errors: None, - }; - - registrations.push(cip36); - } - Ok(registrations) -} - -/// Sort latest registrations for a given stake address, sort by slot no and return -/// latest. -fn sort_latest_registration(mut registrations: Vec) -> anyhow::Result { - registrations.sort_by_key(|registration| Reverse(registration.slot_no)); - registrations.into_iter().next().ok_or(anyhow::anyhow!( - "Can't sort latest registrations by slot no" - )) -} - -/// Get registration given slot# -fn get_registration_given_slot_no( - registrations: Vec, slot_no: SlotNo, -) -> anyhow::Result { - registrations - .into_par_iter() - .find_first(|registration| registration.slot_no == slot_no) - .ok_or(anyhow::anyhow!("Unable to get registration given slot no")) -} - -/// Get invalid registrations for stake addr after given slot# -async fn get_invalid_registrations( - stake_pub_key: Ed25519HexEncodedPublicKey, slot_no: Option, - session: Arc, -) -> anyhow::Result> { - // include any erroneous registrations which occur AFTER the slot# of the last valid - // registration or return all invalids if NO slot# declared. - let slot_no = slot_no.unwrap_or_default(); + session::CassandraSession, + }, + service::common::{ + objects::generic::pagination::CurrentPage, + types::generic::query::pagination::{Limit, Page, Remaining}, + }, +}; - let mut invalid_registrations_iter = GetInvalidRegistrationQuery::execute( +/// Get registrations given a stake address, it can be time specific based on asat param, +/// or the all registrations returned if no asat given. +pub(crate) async fn get_registrations_given_stake_addr( + stake_address: StakeAddress, session: Arc, asat: Option, page: Page, + limit: Limit, invalid: bool, +) -> anyhow::Result { + // Get stake public key associated with given stake address. + let mut stake_pk_stream = GetStakePublicKeyFromStakeAddrQuery::execute( &session, - GetInvalidRegistrationParams::new(stake_pub_key.try_into()?, slot_no), + GetStakePublicKeyFromStakeAddrParams::new(stake_address), ) .await?; - let mut invalid_registrations = Vec::new(); - while let Some(row) = invalid_registrations_iter.next().await { - let row = row?; - - let payment_address = Cip19ShelleyAddress::try_from(row.payment_address).ok(); - let vote_pub_key = Ed25519HexEncodedPublicKey::try_from(row.vote_key).ok(); - - let stake_pub_key = Ed25519HexEncodedPublicKey::try_from(row.stake_public_key.clone()).ok(); + let Some(row_stake_pk) = stake_pk_stream.try_next().await? else { + return Ok(Cip36Registration::NotFound); + }; + let stake_public_key = row_stake_pk.stake_public_key; - invalid_registrations.push(Cip36Details { - slot_no, - stake_pub_key, - vote_pub_key, - nonce: None, - txn: None, - payment_address, - is_payable: row.is_payable.into(), - cip15: (!row.cip36).into(), - errors: Some(ErrorMessage::from(row.problem_report)), - }); - } + let slot_no = asat.unwrap_or(SlotNo::MAXIMUM); + let all_regs = if invalid { + get_invalid_registrations(&session, stake_public_key, slot_no).await? + } else { + get_valid_registrations(&session, stake_public_key, slot_no).await? + }; - Ok(invalid_registrations) + build_response(all_regs, page, limit, invalid, asat) } -/// Get registration given a vote key, time specific based on asat param, +/// Get registrations given a vote key, time specific based on asat param, /// or latest registration returned if no asat given. -pub(crate) async fn get_registration_given_vote_key( +pub(crate) async fn get_registrations_given_vote_key( vote_key: Ed25519HexEncodedPublicKey, session: Arc, asat: Option, -) -> AllRegistration { - let voting_key: Vec = match vote_key.clone().try_into() { - Ok(vote_key) => vote_key, - Err(err) => { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Failed to convert vote key to bytes {err:?}", - )); - }, - }; - - // Get stake addr associated voting key. - let mut stake_addr_iter = match GetStakeAddrFromVoteKeyQuery::execute( + page: Page, limit: Limit, invalid: bool, +) -> anyhow::Result { + let voting_key: Vec = vote_key + .try_into() + .map_err(|err| anyhow::anyhow!("Failed to convert vote key to bytes {err:?}"))?; + + // Get stake public key associated voting key + let stake_pk_stream = GetStakePublicKeyFromVoteKeyQuery::execute( &session, - GetStakeAddrFromVoteKeyParams::new(voting_key), + GetStakePublicKeyFromVoteKeyParams::new(voting_key), ) .await - { - Ok(stake_addr) => stake_addr, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Failed to query stake addr from vote key {err:?}", - )); - }, - }; - - if let Some(row_stake_addr) = stake_addr_iter.next().await { - let row = match row_stake_addr { - Ok(r) => r, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Failed to query stake addr from vote key {err:?}", - )); - }, - }; - - // Stake hash successfully converted into associated stake pub key which we use to lookup - // registrations. - let stake_pub_key = match Ed25519HexEncodedPublicKey::try_from(row.stake_public_key.clone()) - { - Ok(key) => key, - Err(err) => { - return AllRegistration::internal_error(&anyhow::anyhow!( - "Failed to type stake address {err:?}", - )); - }, - }; - - return get_registration_from_stake_addr(stake_pub_key, asat, session, Some(vote_key)) - .await; - } + .map_err(|err| anyhow::anyhow!("Failed to query stake public key from vote key {err:?}",))?; + + let slot_no = asat.unwrap_or(SlotNo::MAXIMUM); + + let all_regs = stake_pk_stream + .map_err(Into::::into) + .try_fold(Vec::new(), |mut all_regs, row_stake_pk| { + async { + let stake_public_key = row_stake_pk.stake_public_key; + let regs = if invalid { + get_invalid_registrations(&session, stake_public_key, slot_no).await? + } else { + get_valid_registrations(&session, stake_public_key, slot_no).await? + }; + all_regs.extend(regs); + Ok(all_regs) + } + }) + .await?; - AllRegistration::With(Cip36Registration::NotFound) + build_response(all_regs, page, limit, invalid, asat) } -/// ALL /// Get all registrations or constrain if slot# given. -pub async fn snapshot(session: Arc, slot_no: Option) -> AllRegistration { - let valid_invalid_queries = future::join( - get_all_registrations(session.clone()), - get_all_invalid_registrations(session.clone()), - ); - - let (valid_registrations, invalid_registrations) = valid_invalid_queries.await; - - // Get ALL registrations - let all_registrations = match valid_registrations { - Ok(all_registrations) => all_registrations, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!("Failed to query ALL {err:?}",)); - }, - }; - - // Get all invalids - let all_invalid_registrations = match invalid_registrations { - Ok(invalids) => invalids, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!("Failed to query ALL {err:?}",)); - }, +pub(crate) async fn snapshot( + session: Arc, asat: Option, page: Page, limit: Limit, invalid: bool, +) -> anyhow::Result { + let slot_no = asat.unwrap_or(SlotNo::MAXIMUM); + let all_regs = if invalid { + get_all_invalid_registrations(&session, slot_no).await? + } else { + get_all_valid_registrations(&session, slot_no).await? }; - let mut all_registrations_after_filtering = Vec::new(); - let mut all_invalids_after_filtering = Vec::new(); + build_response(all_regs, page, limit, invalid, asat) +} - for (stake_public_key, registrations) in all_registrations { - // latest vote key - let vote_pub_key = match latest_vote_key(registrations.clone()) { - Ok(vote_key) => vote_key, - Err(err) => { - error!("Snapshot: no voting keys with any registration {:?}", err); - continue; - }, - }; +/// Get valid cip36 registrations for a given stake public key. +async fn get_valid_registrations( + session: &CassandraSession, stake_public_key: Vec, slot_no: SlotNo, +) -> Result, anyhow::Error> { + let hex_stake_pk = Ed25519HexEncodedPublicKey::try_from(stake_public_key.as_slice()) + .map_err(|err| anyhow::anyhow!("Failed to convert to type stake public key {err}"))?; - // ALL: Snapshot can be constrained into a subset with a time constraint or NOT. - if let Some(slot_no) = slot_no { - // Any registrations that occurred after this Slot are not included in the list. - let filtered_registrations = slot_filter(registrations, slot_no); + let regs_stream = GetRegistrationQuery::execute( + session, + GetRegistrationParams::new(stake_public_key, slot_no), + ) + .await?; - if filtered_registrations.is_empty() { - continue; + regs_stream + .map_err(Into::::into) + .try_fold(Vec::new(), |mut regs, row| { + { + let hex_stake_pk = hex_stake_pk.clone(); + async move { + let Ok(nonce) = u64::try_from(row.nonce) else { + anyhow::bail!("Corrupt valid registration, cannot decode nonce"); + }; + let Ok(raw_nonce) = u64::try_from(row.raw_nonce) else { + anyhow::bail!("Corrupt valid registration, cannot decode raw_nonce"); + }; + + let slot_no = match SlotNo::try_from(u64::from(row.slot_no)) { + Ok(slot_no) => slot_no, + Err(err) => { + anyhow::bail!("Corrupt invalid registration {err}"); + }, + }; + + let payment_address = match Cip19Address::try_from(row.payment_address) { + Ok(payment_addr) => Some(payment_addr), + Err(err) => { + anyhow::bail!( + "Corrupt valid registration, invalid payment_address {err}\n Stake pub key: {}", + *hex_stake_pk + ); + }, + }; + + let vote_pub_key = match Ed25519HexEncodedPublicKey::try_from(row.vote_key) { + Ok(vote_pub_key) => Some(vote_pub_key), + Err(err) => { + anyhow::bail!( + "Corrupt valid registration, invalid vote_pub_key {err}\n Stake pub key:{:?}", + *hex_stake_pk + ); + }, + }; + + let cip36 = Cip36Details { + slot_no, + stake_pub_key: Some(hex_stake_pk), + vote_pub_key, + nonce: Some(nonce.into()), + raw_nonce: Some(raw_nonce.into()), + txn_index: Some(row.txn_index.into()), + payment_address, + is_payable: row.is_payable.into(), + cip15: (!row.cip36).into(), + report: None, + }; + + regs.push(cip36); + Ok(regs) + } } - - all_registrations_after_filtering.push(Cip36RegistrationsForVotingPublicKey { - vote_pub_key, - registrations: filtered_registrations.into(), - }); - } else { - // No slot filtering, return ALL registrations without constraints. - all_registrations_after_filtering.push(Cip36RegistrationsForVotingPublicKey { - vote_pub_key, - registrations: registrations.into(), - }); - } - - // get all invalid registrations for given stake pub key - let invalid_registrations = match all_invalid_registrations.get(&stake_public_key) { - Some(invalids) => invalids.clone(), - None => vec![], - }; - - if let Some(ref slot_no) = slot_no { - // include any erroneous registrations which occur AFTER the slot# of the last valid - // registration or return all if NO slot# declared. - // Any registrations that occurred before this Slot are not included in the list. - let invalids_report_after_filtering = invalid_filter(invalid_registrations, *slot_no); - all_invalids_after_filtering.push(invalids_report_after_filtering); - } else { - all_invalids_after_filtering.push(invalid_registrations); - } - } - - AllRegistration::With(Cip36Registration::Ok(poem_openapi::payload::Json( - Cip36RegistrationList { - slot: slot_no.unwrap_or_default(), - voting_key: all_registrations_after_filtering.into(), - invalid: all_invalids_after_filtering - .into_par_iter() - .flatten() - .collect::>() - .into(), - page: None, - }, - ))) + }) + .await } -/// Get all cip36 registrations. -pub async fn get_all_registrations( - session: Arc, -) -> Result>, anyhow::Error> { - let mut registrations_iter = - GetAllRegistrationsQuery::execute(&session, GetAllRegistrationsParams {}).await?; +/// Get invalid registrations for stake public key +async fn get_invalid_registrations( + session: &CassandraSession, stake_public_key: Vec, slot_no: SlotNo, +) -> anyhow::Result> { + let invalid_regs_stream = GetInvalidRegistrationQuery::execute( + session, + GetInvalidRegistrationParams::new(stake_public_key, slot_no), + ) + .await?; - let registrations_map: DashMap> = DashMap::new(); + invalid_regs_stream + .map_err(Into::::into) + .try_fold(Vec::new(), |mut regs, row| { + async move { + let Ok(nonce) = u64::try_from(row.nonce) else { + anyhow::bail!("Corrupt invalid registration, cannot decode nonce"); + }; + let Ok(raw_nonce) =u64::try_from(row.raw_nonce) else { + anyhow::bail!("Corrupt invalid registration, cannot decode raw_nonce"); + }; + let slot_no = match SlotNo::try_from(u64::from(row.slot_no)) { + Ok(slot_no) => slot_no, + Err(err) => { + anyhow::bail!("Corrupt invalid registration {err}"); + }, + }; + let payment_address = Cip19Address::try_from(row.payment_address).ok(); + let vote_pub_key = Ed25519HexEncodedPublicKey::try_from(row.vote_key).ok(); + let stake_pub_key = + Ed25519HexEncodedPublicKey::try_from(row.stake_public_key.clone()).ok(); + let report: serde_json::Value = + serde_json::from_str(&row.problem_report).map_err(|e| { + anyhow::anyhow!("Invalid CIP36 registration problem report should me JSON encodable, err: {e}") + })?; + + regs.push(Cip36Details { + slot_no, + stake_pub_key, + vote_pub_key, + nonce: Some(nonce.into()), + raw_nonce: Some(raw_nonce.into()), + txn_index: Some(row.txn_index.into()), + payment_address, + is_payable: row.is_payable.into(), + cip15: (!row.cip36).into(), + report: Some(report.into()), + }); + Ok(regs) + } + }) + .await +} - while let Some(row) = registrations_iter.next().await { - let row = row?; +/// Get all valid cip36 registrations. +async fn get_all_valid_registrations( + session: &CassandraSession, slot_no: SlotNo, +) -> Result, anyhow::Error> { + let regs_stream = + GetAllRegistrationsQuery::execute(session, GetAllRegistrationsParams::new(slot_no)).await?; - let nonce = if let Some(nonce) = row.nonce.into_parts().1.to_u64_digits().first() { - *nonce - } else { - continue; + regs_stream.map_err(Into::::into).try_fold(Vec::new(), |mut regs, row| + async move { + let Ok(nonce) = u64::try_from(row.nonce) else { + anyhow::bail!("Corrupt valid registration, cannot decode nonce"); }; - - let slot_no = if let Some(slot_no) = row.slot_no.into_parts().1.to_u64_digits().first() { - *slot_no - } else { - continue; + let Ok(raw_nonce) = u64::try_from(row.raw_nonce) else { + anyhow::bail!("Corrupt valid registration, cannot decode raw_nonce"); }; - let slot_no = match SlotNo::try_from(slot_no) { + let slot_no = match SlotNo::try_from(u64::from(row.slot_no)) { Ok(slot_no) => slot_no, Err(err) => { - error!("Corrupt valid registration {:?}", err); - // This should NOT happen, valid registrations should be infallible. - // If it happens, there is an indexing issue. - continue; + anyhow::bail!("Corrupt invalid registration {err}"); }, }; @@ -497,32 +267,21 @@ pub async fn get_all_registrations( { Ok(stake_pub_key) => Some(stake_pub_key), Err(err) => { - error!("Corrupt valid registration {:?}", err); - // This should NOT happen, valid registrations should be infallible. - // If it happens, there is an indexing issue. - continue; + anyhow::bail!("Corrupt valid registration, invalid stake_pub_key {err}"); }, }; - let payment_address = match Cip19ShelleyAddress::try_from(row.payment_address) { + let payment_address = match Cip19Address::try_from(row.payment_address) { Ok(payment_addr) => Some(payment_addr), Err(err) => { - error!( - "Corrupt valid registration {:?}\n Stake pub key:{:?}", - err, stake_pub_key - ); - continue; + anyhow::bail!("Corrupt valid registration, invalid payment_address {err}\n Stake pub key:{stake_pub_key:?}"); }, }; let vote_pub_key = match Ed25519HexEncodedPublicKey::try_from(row.vote_key) { Ok(vote_pub_key) => Some(vote_pub_key), Err(err) => { - error!( - "Corrupt valid registration {:?}\n Stake pub key:{:?}", - err, stake_pub_key - ); - continue; + anyhow::bail!("Corrupt valid registration, invalid vote_pub_key {err}\n Stake pub key:{stake_pub_key:?}"); }, }; @@ -530,114 +289,151 @@ pub async fn get_all_registrations( slot_no, stake_pub_key, vote_pub_key, - nonce: Some(Nonce::from(nonce)), - txn: Some(row.txn_index.into()), + nonce: Some(nonce.into()), + raw_nonce: Some(raw_nonce.into()), + txn_index: Some(row.txn_index.into()), payment_address, is_payable: row.is_payable.into(), cip15: (!row.cip36).into(), - errors: None, + report: None, }; - - if let Some(mut v) = registrations_map.get_mut(&Ed25519HexEncodedPublicKey::try_from( - row.stake_public_key.clone(), - )?) { - v.push(cip36); - continue; - }; - - registrations_map.insert( - Ed25519HexEncodedPublicKey::try_from(row.stake_public_key)?, - vec![cip36], - ); - } - - Ok(registrations_map) + regs.push(cip36); + Ok(regs) + }).await } /// Get all invalid registrations async fn get_all_invalid_registrations( - session: Arc, -) -> Result>, anyhow::Error> { - let invalids_map: DashMap> = DashMap::new(); - - let mut invalid_registrations_iter = - GetAllInvalidRegistrationsQuery::execute(&session, GetAllInvalidRegistrationsParams {}) - .await?; - - while let Some(row) = invalid_registrations_iter.next().await { - let row = row?; - - let slot_no = if let Some(slot_no) = row.slot_no.into_parts().1.to_u64_digits().first() { - *slot_no - } else { - continue; - }; - - let slot_no = SlotNo::try_from(slot_no).unwrap_or_default(); - - let payment_addr = Cip19ShelleyAddress::try_from(row.payment_address).ok(); - - let vote_pub_key = Ed25519HexEncodedPublicKey::try_from(row.vote_key).ok(); - - let stake_pub_key = Ed25519HexEncodedPublicKey::try_from(row.stake_public_key.clone()).ok(); - - let invalid = Cip36Details { - slot_no, - stake_pub_key, - vote_pub_key, - nonce: None, - txn: None, - payment_address: payment_addr, - is_payable: row.is_payable.into(), - cip15: (!row.cip36).into(), - errors: Some(ErrorMessage::from(row.problem_report)), - }; + session: &CassandraSession, slot_no: SlotNo, +) -> Result, anyhow::Error> { + let invalid_regs_stream = GetAllInvalidRegistrationsQuery::execute( + session, + GetAllInvalidRegistrationsParams::new(slot_no), + ) + .await?; - if let Some(mut v) = invalids_map.get_mut(&Ed25519HexEncodedPublicKey::try_from( - row.stake_public_key.clone(), - )?) { - v.push(invalid); - continue; - }; + invalid_regs_stream + .map_err(Into::::into) + .try_fold(Vec::new(), |mut regs, row| { + async move { + let Ok(nonce) = u64::try_from(row.nonce) else { + anyhow::bail!("Corrupt valid registration, cannot decode nonce"); + }; + let Ok(raw_nonce) = u64::try_from(row.raw_nonce) else { + anyhow::bail!("Corrupt valid registration, cannot decode raw_nonce"); + }; + let slot_no = match SlotNo::try_from(u64::from(row.slot_no)) { + Ok(slot_no) => slot_no, + Err(err) => { + anyhow::bail!("Corrupt invalid registration {err}"); + }, + }; + + let payment_addr = Cip19Address::try_from(row.payment_address).ok(); + + let vote_pub_key = Ed25519HexEncodedPublicKey::try_from(row.vote_key).ok(); + + let stake_pub_key = + Ed25519HexEncodedPublicKey::try_from(row.stake_public_key.clone()).ok(); + let report: serde_json::Value = + serde_json::from_str(&row.problem_report).map_err(|e| { + anyhow::anyhow!( + "Invalid CIP36 registration problem report should me JSON encodable, err: {e}" + ) + })?; + + let invalid = Cip36Details { + slot_no, + stake_pub_key, + vote_pub_key, + nonce: Some(nonce.into()), + raw_nonce: Some(raw_nonce.into()), + txn_index: Some(row.txn_index.into()), + payment_address: payment_addr, + is_payable: row.is_payable.into(), + cip15: (!row.cip36).into(), + report: Some(report.into()), + }; + regs.push(invalid); + Ok(regs) + } + }) + .await +} - invalids_map.insert( - Ed25519HexEncodedPublicKey::try_from(row.stake_public_key)?, - vec![invalid], - ); +/// Build a final response which will contain all found registrations, +/// sort them and apply pagination. +fn build_response( + regs: Vec, page: Page, limit: Limit, invalid: bool, slot: Option, +) -> anyhow::Result { + if regs.is_empty() { + return Ok(Cip36Registration::NotFound); } + let regs = sort_registrations(regs); + let (regs, remaining) = paginate_registrations(regs, page.into(), limit.into())?; - Ok(invalids_map) -} - -/// Filter out any registrations that occurred after this Slot no -fn slot_filter(registrations: Vec, slot_no: SlotNo) -> Vec { - registrations - .into_par_iter() - .filter(|registration| registration.slot_no < slot_no) - .collect() + Ok(Cip36Registration::Ok(poem_openapi::payload::Json( + Cip36RegistrationList { + slot, + is_valid: (!invalid).into(), + regs: regs.into(), + page: Some( + CurrentPage { + page, + limit, + remaining, + } + .into(), + ), + }, + ))) } -/// Stake addr may have multiple registrations and multiple vote key associations, filter -/// out latest vote key. -fn latest_vote_key( - mut registrations: Vec, -) -> anyhow::Result { - registrations.sort_by_key(|registration| Reverse(registration.slot_no)); - for registration in registrations { - if let Some(vote_key) = registration.vote_pub_key { - return Ok(vote_key); - } - } - - Err(anyhow::anyhow!( - "No vote keys associated with any registration" - )) +/// Sort registrations by slot number, nonce, and transaction offset. If `slot_no` is the +/// same, the registration with the highest `nonce` wins. If `nonce` is the same, the +/// registration with the highest `txn_offset` wins. +fn sort_registrations(mut regs: Vec) -> Vec { + // Sort registrations by slot_no, nonce, and txn_offset in descending order + regs.sort_by(|a, b| { + // Compare by slot_no (descending) + // Safe to use 0 value, since ordering in descending order + b.slot_no + .cmp(&a.slot_no) + .then_with(|| { + b.nonce + .clone() + .unwrap_or_default() + .cmp(&a.nonce.clone().unwrap_or_default()) + }) // If slot_no is the same, compare by nonce (descending) + .then_with(|| { + b.txn_index + .clone() + .unwrap_or_default() + .cmp(&a.txn_index.clone().unwrap_or_default()) + }) // If nonce is also the same, compare by txn_offset (descending) + }); + + regs } -/// Filter out any invalid registrations that occurred before this Slot no -fn invalid_filter(registrations: Vec, slot_no: SlotNo) -> Vec { - registrations - .into_par_iter() - .filter(|registration| registration.slot_no > slot_no) - .collect() +/// Paginate the registrations. +fn paginate_registrations( + regs: Vec, page: u32, limit: u32, +) -> anyhow::Result<(Vec, Remaining)> { + let total_reg = regs.len(); + + let page_usize: usize = page.try_into()?; + let limit_usize: usize = limit.try_into()?; + let start_index: usize = page_usize.saturating_mul(limit_usize); + let paginated_regs: Vec<_> = regs + .into_iter() + .skip(start_index) + .take(limit_usize) + .collect(); + let reg_count = paginated_regs.len(); + + let remaining = Remaining::calculate(page, limit, total_reg.try_into()?, reg_count.try_into()?); + + // Return the paginated valid, and remaining. + Ok((paginated_regs, remaining)) } diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs b/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs index f2c14a25264..54b04851f2e 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/mod.rs @@ -10,6 +10,5 @@ pub(crate) mod cip19_stake_address; pub(crate) mod hash28; pub(crate) mod nonce; pub(crate) mod query; -pub(crate) mod registration_list; pub(crate) mod slot_no; pub(crate) mod txn_index; diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/registration_list.rs b/catalyst-gateway/bin/src/service/common/types/cardano/registration_list.rs deleted file mode 100644 index a9947992f89..00000000000 --- a/catalyst-gateway/bin/src/service/common/types/cardano/registration_list.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Implement newtype of `RegistrationList` - -use poem_openapi::{ - registry::MetaSchema, - types::{Example, ToJSON}, -}; - -use crate::service::{ - api::cardano::cip36::response::Cip36Details, common::types::array_types::impl_array_types, -}; - -// List of CIP-36 Registrations -impl_array_types!( - RegistrationCip36List, - Cip36Details, - Some(MetaSchema { - example: Self::example().to_json(), - max_items: Some(100), - items: Some(Box::new(Cip36Details::schema_ref())), - ..poem_openapi::registry::MetaSchema::ANY - }) -); - -impl Example for RegistrationCip36List { - fn example() -> Self { - Self(vec![Example::example()]) - } -} diff --git a/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs b/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs index 9596c75584b..e6d29ce97c5 100644 --- a/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs +++ b/catalyst-gateway/bin/src/service/common/types/generic/boolean.rs @@ -11,4 +11,4 @@ impl Example for BooleanFlag { fn example() -> Self { Self(true) } -} \ No newline at end of file +} From 3a5242cc61827bf96d37324a1ca68b14f439c283 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 31 Mar 2025 23:49:55 +0300 Subject: [PATCH 04/10] wip --- ...ress.cql => get_stake_pk_w_stake_addr.cql} | 0 .../bin/src/db/index/queries/mod.rs | 21 ++--- .../queries/registrations/get_all_invalids.rs | 34 +++++-- .../registrations/get_all_registrations.rs | 23 ++++- ...rom_stake_addr.rs => get_from_stake_pk.rs} | 27 +++--- .../queries/registrations/get_invalid.rs | 42 +++++---- ...ess.rs => get_stake_pk_from_stake_addr.rs} | 36 ++++---- .../src/db/index/queries/registrations/mod.rs | 4 +- .../bin/src/db/index/tests/scylla_queries.rs | 2 +- .../src/service/api/cardano/cip36/endpoint.rs | 89 ++++++++++--------- .../src/service/api/cardano/cip36/filter.rs | 5 +- 11 files changed, 169 insertions(+), 114 deletions(-) rename catalyst-gateway/bin/src/db/index/queries/cql/{get_stake_addr_w_stake_address.cql => get_stake_pk_w_stake_addr.cql} (100%) rename catalyst-gateway/bin/src/db/index/queries/registrations/{get_from_stake_addr.rs => get_from_stake_pk.rs} (76%) rename catalyst-gateway/bin/src/db/index/queries/registrations/{get_from_stake_address.rs => get_stake_pk_from_stake_addr.rs} (53%) diff --git a/catalyst-gateway/bin/src/db/index/queries/cql/get_stake_addr_w_stake_address.cql b/catalyst-gateway/bin/src/db/index/queries/cql/get_stake_pk_w_stake_addr.cql similarity index 100% rename from catalyst-gateway/bin/src/db/index/queries/cql/get_stake_addr_w_stake_address.cql rename to catalyst-gateway/bin/src/db/index/queries/cql/get_stake_pk_w_stake_addr.cql diff --git a/catalyst-gateway/bin/src/db/index/queries/mod.rs b/catalyst-gateway/bin/src/db/index/queries/mod.rs index cb49f6d241a..a4320536df6 100644 --- a/catalyst-gateway/bin/src/db/index/queries/mod.rs +++ b/catalyst-gateway/bin/src/db/index/queries/mod.rs @@ -14,9 +14,9 @@ use anyhow::bail; use crossbeam_skiplist::SkipMap; use registrations::{ get_all_invalids::GetAllInvalidRegistrationsQuery, - get_all_registrations::GetAllRegistrationsQuery, get_from_stake_addr::GetRegistrationQuery, - get_from_stake_address::GetStakeAddrQuery, + get_all_registrations::GetAllRegistrationsQuery, get_from_stake_pk::GetRegistrationQuery, get_from_vote_key::GetStakePublicKeyFromVoteKeyQuery, get_invalid::GetInvalidRegistrationQuery, + get_stake_pk_from_stake_addr::GetStakePublicKeyFromStakeAddrQuery, }; use scylla::{ batch::Batch, prepared_statement::PreparedStatement, serialize::row::SerializeRow, @@ -91,8 +91,8 @@ pub(crate) enum PreparedSelectQuery { RegistrationFromStakeAddr, /// Get invalid Registration InvalidRegistrationsFromStakeAddr, - /// Get stake addr from stake hash - StakeAddrFromStakeHash, + /// Get stake public key from stake address + StakePublicKeyFromStakeAddr, /// Get stake addr from vote key StakeAddrFromVoteKey, /// Get Catalyst ID by transaction ID. @@ -154,8 +154,8 @@ pub(crate) struct PreparedQueries { native_assets_by_stake_address_query: PreparedStatement, /// Get registrations registration_from_stake_addr_query: PreparedStatement, - /// stake addr from stake hash - stake_addr_from_stake_address_query: PreparedStatement, + /// stake pk from stake addr + stake_pk_from_stake_address_query: PreparedStatement, /// stake addr from vote key stake_addr_from_vote_key_query: PreparedStatement, /// Get invalid registrations @@ -202,7 +202,8 @@ impl PreparedQueries { GetAssetsByStakeAddressQuery::prepare(session.clone()).await; let registration_from_stake_addr_query = GetRegistrationQuery::prepare(session.clone()).await; - let stake_addr_from_stake_address = GetStakeAddrQuery::prepare(session.clone()).await; + let stake_pk_from_stake_address = + GetStakePublicKeyFromStakeAddrQuery::prepare(session.clone()).await; let stake_addr_from_vote_key = GetStakePublicKeyFromVoteKeyQuery::prepare(session.clone()).await; let invalid_registrations = GetInvalidRegistrationQuery::prepare(session.clone()).await; @@ -258,7 +259,7 @@ impl PreparedQueries { catalyst_id_for_stake_address_insert_queries, native_assets_by_stake_address_query: native_assets_by_stake_address_query?, registration_from_stake_addr_query: registration_from_stake_addr_query?, - stake_addr_from_stake_address_query: stake_addr_from_stake_address?, + stake_pk_from_stake_address_query: stake_pk_from_stake_address?, stake_addr_from_vote_key_query: stake_addr_from_vote_key?, invalid_registrations_from_stake_addr_query: invalid_registrations?, sync_status_insert, @@ -348,8 +349,8 @@ impl PreparedQueries { PreparedSelectQuery::RegistrationFromStakeAddr => { &self.registration_from_stake_addr_query }, - PreparedSelectQuery::StakeAddrFromStakeHash => { - &self.stake_addr_from_stake_address_query + PreparedSelectQuery::StakePublicKeyFromStakeAddr => { + &self.stake_pk_from_stake_address_query }, PreparedSelectQuery::StakeAddrFromVoteKey => &self.stake_addr_from_vote_key_query, PreparedSelectQuery::InvalidRegistrationsFromStakeAddr => { diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_invalids.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_invalids.rs index 8b475e5b968..0ffb251ae94 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_invalids.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_invalids.rs @@ -2,15 +2,19 @@ use std::sync::Arc; +use cardano_blockchain_types::Slot; use scylla::{ prepared_statement::PreparedStatement, transport::iterator::TypedRowStream, DeserializeRow, SerializeRow, Session, }; use tracing::error; -use crate::db::index::{ - queries::{PreparedQueries, PreparedSelectQuery}, - session::CassandraSession, +use crate::db::{ + index::{ + queries::{PreparedQueries, PreparedSelectQuery}, + session::CassandraSession, + }, + types::{DbSlot, DbTxnIndex}, }; /// Get all invalid registrations @@ -18,17 +22,35 @@ const GET_ALL_INVALIDS: &str = include_str!("../cql/get_all_invalids.cql"); /// Get all invalid registrations #[derive(SerializeRow)] -pub(crate) struct GetAllInvalidRegistrationsParams {} +pub(crate) struct GetAllInvalidRegistrationsParams { + /// Block Slot Number. + slot_no: DbSlot, +} + +impl GetAllInvalidRegistrationsParams { + /// Create a new instance of [`GetAllInvalidRegistrationsParams`] + pub(crate) fn new(slot_no: Slot) -> Self { + Self { + slot_no: slot_no.into(), + } + } +} /// Get all invalid registrations details for snapshot. #[derive(DeserializeRow)] pub(crate) struct GetAllInvalidRegistrationsQuery { + /// Nonce value after normalization. + pub nonce: num_bigint::BigInt, + /// Raw Nonce value. + pub raw_nonce: num_bigint::BigInt, + /// Slot Number the invalid CIP36 registration is in. + pub slot_no: DbSlot, + /// Transaction Index. + pub txn_index: DbTxnIndex, /// Error report pub problem_report: String, /// Full Stake Address (not hashed, 32 byte ED25519 Public key). pub stake_public_key: Vec, - /// Slot Number - pub slot_no: num_bigint::BigInt, /// Voting Public Key pub vote_key: Vec, /// Full Payment Address (not hashed, 32 byte ED25519 Public key). diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_registrations.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_registrations.rs index 1f0cbed63bf..6502a729a00 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_registrations.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/get_all_registrations.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use cardano_blockchain_types::Slot; use scylla::{ prepared_statement::PreparedStatement, transport::iterator::TypedRowStream, DeserializeRow, SerializeRow, Session, @@ -13,7 +14,7 @@ use crate::db::{ queries::{PreparedQueries, PreparedSelectQuery}, session::CassandraSession, }, - types::DbTxnIndex, + types::{DbSlot, DbTxnIndex}, }; /// Get all registrations @@ -21,7 +22,19 @@ const GET_ALL_REGISTRATIONS: &str = include_str!("../cql/get_all_registrations.c /// Get all registrations #[derive(SerializeRow)] -pub(crate) struct GetAllRegistrationsParams {} +pub(crate) struct GetAllRegistrationsParams { + /// Block Slot Number. + slot_no: DbSlot, +} + +impl GetAllRegistrationsParams { + /// Create a new instance of [`GetAllRegistrationsParams`] + pub(crate) fn new(slot_no: Slot) -> Self { + Self { + slot_no: slot_no.into(), + } + } +} /// Get all registration details for snapshot. #[derive(DeserializeRow)] @@ -30,10 +43,12 @@ pub(crate) struct GetAllRegistrationsQuery { pub stake_public_key: Vec, /// Nonce value after normalization. pub nonce: num_bigint::BigInt, - /// Slot Number the cert is in. - pub slot_no: num_bigint::BigInt, + /// Slot Number the CIP36 registration is in. + pub slot_no: DbSlot, /// Transaction Index. pub txn_index: DbTxnIndex, + /// Raw Nonce value. + pub raw_nonce: num_bigint::BigInt, /// Voting Public Key pub vote_key: Vec, /// Full Payment Address (not hashed, 32 byte ED25519 Public key). diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_addr.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_pk.rs similarity index 76% rename from catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_addr.rs rename to catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_pk.rs index f1cdf090c4a..304823aefa0 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_addr.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_pk.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use cardano_blockchain_types::Slot; use scylla::{ prepared_statement::PreparedStatement, transport::iterator::TypedRowStream, DeserializeRow, SerializeRow, Session, @@ -16,21 +17,25 @@ use crate::db::{ types::{DbSlot, DbTxnIndex}, }; -/// Get registrations from stake addr query. -const GET_REGISTRATIONS_FROM_STAKE_ADDR_QUERY: &str = +/// Get registrations from stake public key query. +const GET_REGISTRATIONS_FROM_STAKE_PK_QUERY: &str = include_str!("../cql/get_registrations_w_stake_addr.cql"); /// Get registration #[derive(SerializeRow)] pub(crate) struct GetRegistrationParams { - /// Stake address. - pub stake_public_key: Vec, + /// Stake public key. + stake_public_key: Vec, + /// Block Slot Number. + slot_no: DbSlot, } -impl From> for GetRegistrationParams { - fn from(value: Vec) -> Self { - GetRegistrationParams { - stake_public_key: value, +impl GetRegistrationParams { + /// Create a new instance of [`GetRegistrationParams`] + pub(crate) fn new(stake_public_key: Vec, slot_no: Slot) -> Self { + Self { + stake_public_key, + slot_no: slot_no.into(), } } } @@ -44,6 +49,8 @@ pub(crate) struct GetRegistrationQuery { pub slot_no: DbSlot, /// Transaction Index. pub txn_index: DbTxnIndex, + /// Raw Nonce value. + pub raw_nonce: num_bigint::BigInt, /// Voting Public Key pub vote_key: Vec, /// Full Payment Address (not hashed, 32 byte ED25519 Public key). @@ -59,13 +66,13 @@ impl GetRegistrationQuery { pub(crate) async fn prepare(session: Arc) -> anyhow::Result { PreparedQueries::prepare( session, - GET_REGISTRATIONS_FROM_STAKE_ADDR_QUERY, + GET_REGISTRATIONS_FROM_STAKE_PK_QUERY, scylla::statement::Consistency::All, true, ) .await .inspect_err(|error| error!(error=%error, "Failed to prepare get registration from stake address query.")) - .map_err(|error| anyhow::anyhow!("{error}\n--\n{GET_REGISTRATIONS_FROM_STAKE_ADDR_QUERY}")) + .map_err(|error| anyhow::anyhow!("{error}\n--\n{GET_REGISTRATIONS_FROM_STAKE_PK_QUERY}")) } /// Executes get registration info for given stake addr query. diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/get_invalid.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/get_invalid.rs index bfca481f7a4..f620f2078d8 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/get_invalid.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/get_invalid.rs @@ -2,42 +2,40 @@ use std::sync::Arc; +use cardano_blockchain_types::Slot; use scylla::{ prepared_statement::PreparedStatement, transport::iterator::TypedRowStream, DeserializeRow, SerializeRow, Session, }; use tracing::error; -use crate::{ - db::{ - index::{ - queries::{PreparedQueries, PreparedSelectQuery}, - session::CassandraSession, - }, - types::DbSlot, +use crate::db::{ + index::{ + queries::{PreparedQueries, PreparedSelectQuery}, + session::CassandraSession, }, - service::common::types::cardano::slot_no::SlotNo, + types::{DbSlot, DbTxnIndex}, }; -/// Get invalid registrations from stake addr query. -const GET_INVALID_REGISTRATIONS_FROM_STAKE_ADDR_QUERY: &str = +/// Get invalid registrations from stake public key query. +const GET_INVALID_REGISTRATIONS_FROM_STAKE_PK_QUERY: &str = include_str!("../cql/get_invalid_registration_w_stake_addr.cql"); /// Get registration #[derive(SerializeRow)] pub(crate) struct GetInvalidRegistrationParams { - /// Stake address. - pub stake_public_key: Vec, - /// Block Slot Number when spend occurred. + /// Stake public key. + stake_public_key: Vec, + /// Block Slot Number. slot_no: DbSlot, } impl GetInvalidRegistrationParams { /// Create a new instance of [`GetInvalidRegistrationParams`] - pub(crate) fn new(stake_public_key: Vec, slot_no: SlotNo) -> GetInvalidRegistrationParams { + pub(crate) fn new(stake_public_key: Vec, slot_no: Slot) -> GetInvalidRegistrationParams { Self { stake_public_key, - slot_no: u64::from(slot_no).into(), + slot_no: slot_no.into(), } } } @@ -45,9 +43,17 @@ impl GetInvalidRegistrationParams { /// Get invalid registrations given stake address. #[derive(DeserializeRow)] pub(crate) struct GetInvalidRegistrationQuery { + /// Nonce value after normalization. + pub nonce: num_bigint::BigInt, + /// Raw Nonce value. + pub raw_nonce: num_bigint::BigInt, + /// Slot Number the invalid CIP 36 registration is in. + pub slot_no: DbSlot, + /// Transaction Index. + pub txn_index: DbTxnIndex, /// Error report pub problem_report: String, - /// Full Stake Address (not hashed, 32 byte ED25519 Public key). + /// Full Stake Public Key (not hashed, 32 byte ED25519 Public key). pub stake_public_key: Vec, /// Voting Public Key pub vote_key: Vec, @@ -64,14 +70,14 @@ impl GetInvalidRegistrationQuery { pub(crate) async fn prepare(session: Arc) -> anyhow::Result { PreparedQueries::prepare( session, - GET_INVALID_REGISTRATIONS_FROM_STAKE_ADDR_QUERY, + GET_INVALID_REGISTRATIONS_FROM_STAKE_PK_QUERY, scylla::statement::Consistency::All, true, ) .await .inspect_err(|error| error!(error=%error, "Failed to prepare get invalid registration from stake address query.")) .map_err(|error| { - anyhow::anyhow!("{error}\n--\n{GET_INVALID_REGISTRATIONS_FROM_STAKE_ADDR_QUERY}") + anyhow::anyhow!("{error}\n--\n{GET_INVALID_REGISTRATIONS_FROM_STAKE_PK_QUERY}") }) } diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_address.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/get_stake_pk_from_stake_addr.rs similarity index 53% rename from catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_address.rs rename to catalyst-gateway/bin/src/db/index/queries/registrations/get_stake_pk_from_stake_addr.rs index 5f79629f457..ad47c13e2dd 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/get_from_stake_address.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/get_stake_pk_from_stake_addr.rs @@ -1,4 +1,4 @@ -//! Get stake addr from stake address +//! Get stake public key from stake address. use std::sync::Arc; @@ -17,34 +17,34 @@ use crate::db::{ types::DbStakeAddress, }; -/// Get stake addr from stake hash query string. -const GET_QUERY: &str = include_str!("../cql/get_stake_addr_w_stake_address.cql"); +/// Get stake public key from stake address query string. +const GET_QUERY: &str = include_str!("../cql/get_stake_pk_w_stake_addr.cql"); -/// Get stake addr +/// Get stake public key from stake address parameters #[derive(SerializeRow)] -pub(crate) struct GetStakeAddrParams { - /// Stake hash. +pub(crate) struct GetStakePublicKeyFromStakeAddrParams { + /// Stake address. pub stake_address: DbStakeAddress, } -impl GetStakeAddrParams { - /// Create a new instance of [`GetStakeAddrParams`] - pub(crate) fn new(stake_address: StakeAddress) -> GetStakeAddrParams { +impl GetStakePublicKeyFromStakeAddrParams { + /// Create a new instance of [`GetStakePublicKeyFromStakeAddrParams`] + pub(crate) fn new(stake_address: StakeAddress) -> GetStakePublicKeyFromStakeAddrParams { Self { stake_address: stake_address.into(), } } } -/// Get stake addr from stake hash query. +/// Get stake public key from stake address query. #[derive(DeserializeRow)] -pub(crate) struct GetStakeAddrQuery { +pub(crate) struct GetStakePublicKeyFromStakeAddrQuery { /// Full Stake Address (not hashed, 32 byte ED25519 Public key). pub stake_public_key: Vec, } -impl GetStakeAddrQuery { - /// Prepares a get get stake addr from stake hash query. +impl GetStakePublicKeyFromStakeAddrQuery { + /// Prepares a get get stake public key from stake address query. pub(crate) async fn prepare(session: Arc) -> anyhow::Result { PreparedQueries::prepare( session, @@ -54,19 +54,19 @@ impl GetStakeAddrQuery { ) .await .inspect_err( - |error| error!(error=%error, "Failed to prepare get stake addr from stake hash query."), + |error| error!(error=%error, "Failed to prepare get stake public key from stake address query."), ) .map_err(|error| anyhow::anyhow!("{error}\n--\n{GET_QUERY}")) } /// Executes a get txi by transaction hashes query. pub(crate) async fn execute( - session: &CassandraSession, params: GetStakeAddrParams, - ) -> anyhow::Result> { + session: &CassandraSession, params: GetStakePublicKeyFromStakeAddrParams, + ) -> anyhow::Result> { let iter = session - .execute_iter(PreparedSelectQuery::StakeAddrFromStakeHash, params) + .execute_iter(PreparedSelectQuery::StakePublicKeyFromStakeAddr, params) .await? - .rows_stream::()?; + .rows_stream::()?; Ok(iter) } diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs index 7b4e24ca9d9..537da440483 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs @@ -2,7 +2,7 @@ pub(crate) mod get_all_invalids; pub(crate) mod get_all_registrations; -pub(crate) mod get_from_stake_addr; -pub(crate) mod get_from_stake_address; +pub(crate) mod get_from_stake_pk; +pub(crate) mod get_stake_pk_from_stake_addr; pub(crate) mod get_from_vote_key; pub(crate) mod get_invalid; diff --git a/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs b/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs index b973c1d87f3..5c97a189689 100644 --- a/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs +++ b/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs @@ -13,7 +13,7 @@ use crate::{ queries::{ rbac, registrations::{ - get_from_stake_addr::*, get_from_stake_address::*, get_from_vote_key::*, + get_from_stake_pk::*, get_stake_pk_from_stake_addr::*, get_from_vote_key::*, get_invalid::*, }, staked_ada::{ diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs index 99ea4160402..e675f313919 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs @@ -5,7 +5,7 @@ use tracing::error; use self::cardano::query::stake_or_voter::StakeAddressOrPublicKey; use super::{ cardano::{self}, - filter::{get_registration_given_stake_key_hash, get_registration_given_vote_key, snapshot}, + filter::{get_registrations_given_stake_addr, get_registrations_given_vote_key, snapshot}, response, SlotNo, }; use crate::{ @@ -19,8 +19,8 @@ use crate::{ /// Process the endpoint operation pub(crate) async fn cip36_registrations( lookup: Option, asat: Option, - _page: common::types::generic::query::pagination::Page, - _limit: common::types::generic::query::pagination::Limit, _invalid: bool, + page: common::types::generic::query::pagination::Page, + limit: common::types::generic::query::pagination::Limit, invalid: bool, ) -> AllRegistration { let Some(session) = CassandraSession::get(true) else { error!("Failed to acquire db session"); @@ -30,50 +30,53 @@ pub(crate) async fn cip36_registrations( ); }; - if let Some(stake_or_voter) = lookup { - match StakeAddressOrPublicKey::from(stake_or_voter) { - StakeAddressOrPublicKey::Address(cip19_stake_address) => { - // Typically, a stake address will start with 'stake1', - // We need to convert this to a stake hash as per our data model to then find the, - // Full Stake Public Key (32 byte Ed25519 Public key, not hashed). - // We then get the latest registration or from a specific time as optionally - // specified in the query parameter. This can be represented as either - // the blockchains slot number or a unix timestamp. - let address = match cip19_stake_address.try_into() { - Ok(a) => a, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Given stake pub key is corrupt {:?}", - err - )); - }, - }; + let Some(lookup) = lookup else { + return response::Cip36Registration::NotFound.into(); + }; + + let res = match StakeAddressOrPublicKey::from(lookup) { + StakeAddressOrPublicKey::Address(cip19_stake_address) => { + // Typically, a stake address will start with 'stake1', + // We need to convert this to a stake hash as per our data model to then find the, + // Full Stake Public Key (32 byte Ed25519 Public key, not hashed). + // We then get the latest registration or from a specific time as optionally + // specified in the query parameter. This can be represented as either + // the blockchains slot number or a unix timestamp. + let address = match cip19_stake_address.try_into() { + Ok(a) => a, + Err(err) => { + return AllRegistration::handle_error(&anyhow::anyhow!( + "Given stake address is corrupted {:?}", + err + )); + }, + }; - return get_registration_given_stake_key_hash(address, session, asat).await; - }, - StakeAddressOrPublicKey::PublicKey(ed25519_hex_encoded_public_key) => { - // As above... - // Except using a voting key. - return get_registration_given_vote_key( - ed25519_hex_encoded_public_key, - session, - asat, - ) - .await; - }, - StakeAddressOrPublicKey::All => + get_registrations_given_stake_addr(address, session, asat, page, limit, invalid).await + }, + StakeAddressOrPublicKey::PublicKey(ed25519_hex_encoded_public_key) => { + // As above... + // Except using a voting key. + get_registrations_given_vote_key( + ed25519_hex_encoded_public_key, + session, + asat, + page, + limit, + invalid, + ) + .await + }, + StakeAddressOrPublicKey::All => { // As above... // Snapshot replacement, returns all registrations or returns a // subset of registrations if constrained by a given time. - { - return snapshot(session, asat).await - }, - }; + snapshot(session, asat, page, limit, invalid).await + }, }; - // If _for is not defined, use the stake addresses defined for Role0 in the _auth - // parameter. _auth not yet implemented, so put placeholder for that, and return not - // found until _auth is implemented. - - response::Cip36Registration::NotFound.into() + match res { + Ok(res) => res.into(), + Err(err) => AllRegistration::handle_error(&err), + } } diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs index 7d1c6625e96..5a55ca6af52 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs @@ -193,7 +193,7 @@ async fn get_invalid_registrations( ) -> anyhow::Result> { let invalid_regs_stream = GetInvalidRegistrationQuery::execute( session, - GetInvalidRegistrationParams::new(stake_public_key, slot_no), + GetInvalidRegistrationParams::new(stake_public_key, slot_no.into()), ) .await?; @@ -245,7 +245,8 @@ async fn get_all_valid_registrations( session: &CassandraSession, slot_no: SlotNo, ) -> Result, anyhow::Error> { let regs_stream = - GetAllRegistrationsQuery::execute(session, GetAllRegistrationsParams::new(slot_no)).await?; + GetAllRegistrationsQuery::execute(session, GetAllRegistrationsParams::new(slot_no.into())) + .await?; regs_stream.map_err(Into::::into).try_fold(Vec::new(), |mut regs, row| async move { From 99690f7921b4d39b2f1d0a9114f370264f45acf1 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 31 Mar 2025 23:54:41 +0300 Subject: [PATCH 05/10] wip --- .../src/service/api/cardano/cip36/endpoint.rs | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs index e675f313919..e564966e56c 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/endpoint.rs @@ -30,53 +30,58 @@ pub(crate) async fn cip36_registrations( ); }; - let Some(lookup) = lookup else { - return response::Cip36Registration::NotFound.into(); - }; + if let Some(stake_or_voter) = lookup { + let res = match StakeAddressOrPublicKey::from(stake_or_voter) { + StakeAddressOrPublicKey::Address(cip19_stake_address) => { + // Typically, a stake address will start with 'stake1', + // We need to convert this to a stake hash as per our data model to then find the, + // Full Stake Public Key (32 byte Ed25519 Public key, not hashed). + // We then get the latest registration or from a specific time as optionally + // specified in the query parameter. This can be represented as either + // the blockchains slot number or a unix timestamp. + let address = match cip19_stake_address.try_into() { + Ok(a) => a, + Err(err) => { + return AllRegistration::handle_error(&anyhow::anyhow!( + "Given stake address is corrupted {:?}", + err + )); + }, + }; - let res = match StakeAddressOrPublicKey::from(lookup) { - StakeAddressOrPublicKey::Address(cip19_stake_address) => { - // Typically, a stake address will start with 'stake1', - // We need to convert this to a stake hash as per our data model to then find the, - // Full Stake Public Key (32 byte Ed25519 Public key, not hashed). - // We then get the latest registration or from a specific time as optionally - // specified in the query parameter. This can be represented as either - // the blockchains slot number or a unix timestamp. - let address = match cip19_stake_address.try_into() { - Ok(a) => a, - Err(err) => { - return AllRegistration::handle_error(&anyhow::anyhow!( - "Given stake address is corrupted {:?}", - err - )); - }, - }; + get_registrations_given_stake_addr(address, session, asat, page, limit, invalid) + .await + }, + StakeAddressOrPublicKey::PublicKey(ed25519_hex_encoded_public_key) => { + // As above... + // Except using a voting key. + get_registrations_given_vote_key( + ed25519_hex_encoded_public_key, + session, + asat, + page, + limit, + invalid, + ) + .await + }, + StakeAddressOrPublicKey::All => { + // As above... + // Snapshot replacement, returns all registrations or returns a + // subset of registrations if constrained by a given time. + snapshot(session, asat, page, limit, invalid).await + }, + }; - get_registrations_given_stake_addr(address, session, asat, page, limit, invalid).await - }, - StakeAddressOrPublicKey::PublicKey(ed25519_hex_encoded_public_key) => { - // As above... - // Except using a voting key. - get_registrations_given_vote_key( - ed25519_hex_encoded_public_key, - session, - asat, - page, - limit, - invalid, - ) - .await - }, - StakeAddressOrPublicKey::All => { - // As above... - // Snapshot replacement, returns all registrations or returns a - // subset of registrations if constrained by a given time. - snapshot(session, asat, page, limit, invalid).await - }, - }; + match res { + Ok(res) => res.into(), + Err(err) => AllRegistration::handle_error(&err), + } + } else { + // If _for is not defined, use the stake addresses defined for Role0 in the _auth + // parameter. _auth not yet implemented, so put placeholder for that, and return not + // found until _auth is implemented. - match res { - Ok(res) => res.into(), - Err(err) => AllRegistration::handle_error(&err), + response::Cip36Registration::NotFound.into() } } From 221362ccc3a945e0c68bacd6c5c95c4d8584d670 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 31 Mar 2025 23:59:50 +0300 Subject: [PATCH 06/10] wip --- .../src/service/api/cardano/cip36/filter.rs | 4 +-- .../src/service/common/types/cardano/nonce.rs | 2 +- .../service/common/types/cardano/txn_index.rs | 2 +- .../types/generic/ed25519_public_key.rs | 26 ++++++++++++++++--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs index 5a55ca6af52..e997c7ffa5f 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/cip36/filter.rs @@ -122,7 +122,7 @@ async fn get_valid_registrations( let regs_stream = GetRegistrationQuery::execute( session, - GetRegistrationParams::new(stake_public_key, slot_no), + GetRegistrationParams::new(stake_public_key, slot_no.into()), ) .await?; @@ -309,7 +309,7 @@ async fn get_all_invalid_registrations( ) -> Result, anyhow::Error> { let invalid_regs_stream = GetAllInvalidRegistrationsQuery::execute( session, - GetAllInvalidRegistrationsParams::new(slot_no), + GetAllInvalidRegistrationsParams::new(slot_no.into()), ) .await?; diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/nonce.rs b/catalyst-gateway/bin/src/service/common/types/cardano/nonce.rs index 479596f071e..cc66d045b66 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/nonce.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/nonce.rs @@ -39,7 +39,7 @@ static SCHEMA: LazyLock = LazyLock::new(|| { }); /// Value of a Nonce. -#[derive(Debug, Eq, PartialEq, Hash, Clone)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, PartialOrd, Ord, Default)] pub(crate) struct Nonce(u64); /// Is the Nonce valid? diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/txn_index.rs b/catalyst-gateway/bin/src/service/common/types/cardano/txn_index.rs index 1351281c6f9..6953eaad45c 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/txn_index.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/txn_index.rs @@ -38,7 +38,7 @@ static SCHEMA: LazyLock = LazyLock::new(|| { }); /// Transaction Index within a block. -#[derive(Debug, Eq, PartialEq, Hash, Clone)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, PartialOrd, Ord, Default)] pub(crate) struct TxnIndex(u16); /// Is the Slot Number valid? diff --git a/catalyst-gateway/bin/src/service/common/types/generic/ed25519_public_key.rs b/catalyst-gateway/bin/src/service/common/types/generic/ed25519_public_key.rs index c771bf657c7..5ffd61f4b81 100644 --- a/catalyst-gateway/bin/src/service/common/types/generic/ed25519_public_key.rs +++ b/catalyst-gateway/bin/src/service/common/types/generic/ed25519_public_key.rs @@ -4,6 +4,7 @@ use std::{ borrow::Cow, + cmp::Ordering, ops::{Deref, DerefMut}, sync::LazyLock, }; @@ -112,13 +113,20 @@ impl TryFrom for Ed25519HexEncodedPublicKey { } } +impl TryFrom<&[u8]> for Ed25519HexEncodedPublicKey { + type Error = anyhow::Error; + + fn try_from(value: &[u8]) -> Result { + let key = ed25519::verifying_key_from_vec(value)?; + Ok(Self(as_hex_string(key.as_ref()))) + } +} + impl TryFrom> for Ed25519HexEncodedPublicKey { type Error = anyhow::Error; fn try_from(value: Vec) -> Result { - let key = ed25519::verifying_key_from_vec(&value)?; - - Ok(Self(as_hex_string(key.as_ref()))) + value.as_slice().try_into() } } @@ -142,6 +150,18 @@ impl TryInto> for Ed25519HexEncodedPublicKey { } } +impl PartialOrd for Ed25519HexEncodedPublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(&other.0)) + } +} + +impl Ord for Ed25519HexEncodedPublicKey { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + #[cfg(test)] mod tests { use super::Ed25519HexEncodedPublicKey; From ffb9ef85e48681824cf83305e47e3eeed1bf69b9 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 1 Apr 2025 00:03:00 +0300 Subject: [PATCH 07/10] wip --- .../bin/src/db/index/tests/scylla_queries.rs | 20 ++++++++++--------- .../service/common/types/cardano/slot_no.rs | 15 +++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs b/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs index 5c97a189689..a44826f0578 100644 --- a/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs +++ b/catalyst-gateway/bin/src/db/index/tests/scylla_queries.rs @@ -13,8 +13,8 @@ use crate::{ queries::{ rbac, registrations::{ - get_from_stake_pk::*, get_stake_pk_from_stake_addr::*, get_from_vote_key::*, - get_invalid::*, + get_from_stake_pk::*, get_from_vote_key::*, get_invalid::*, + get_stake_pk_from_stake_addr::*, }, staked_ada::{ get_assets_by_stake_address::*, get_txi_by_txn_hash::*, @@ -94,7 +94,7 @@ async fn test_get_invalid_registration_w_stake_addr() { let mut row_stream = GetInvalidRegistrationQuery::execute( &session, - GetInvalidRegistrationParams::new(vec![], SlotNo::default()), + GetInvalidRegistrationParams::new(vec![], SlotNo::default().into()), ) .await .unwrap(); @@ -157,9 +157,10 @@ async fn test_get_registrations_w_stake_addr() { panic!("{SESSION_ERR_MSG}"); }; - let mut row_stream = GetRegistrationQuery::execute(&session, GetRegistrationParams { - stake_public_key: vec![], - }) + let mut row_stream = GetRegistrationQuery::execute( + &session, + GetRegistrationParams::new(vec![], SlotNo::default().into()), + ) .await .unwrap(); @@ -175,9 +176,10 @@ async fn test_get_stake_addr_w_stake_key_hash() { panic!("{SESSION_ERR_MSG}"); }; - let mut row_stream = GetStakeAddrQuery::execute(&session, GetStakeAddrParams { - stake_address: stake_address_1().into(), - }) + let mut row_stream = GetStakePublicKeyFromStakeAddrQuery::execute( + &session, + GetStakePublicKeyFromStakeAddrParams::new(stake_address_1()), + ) .await .unwrap(); diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs b/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs index 482747ef362..393753a7033 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs @@ -17,10 +17,6 @@ const TITLE: &str = "Cardano Blockchain Slot Number"; const DESCRIPTION: &str = "The Slot Number of a Cardano Block on the chain."; /// Example. pub(crate) const EXAMPLE: u64 = 1_234_567; -/// Minimum. -const MINIMUM: u64 = 0; -/// Maximum. -const MAXIMUM: u64 = u64::MAX / 2; /// Schema. #[allow(clippy::cast_precision_loss)] @@ -29,8 +25,8 @@ static SCHEMA: LazyLock = LazyLock::new(|| { title: Some(TITLE.to_owned()), description: Some(DESCRIPTION), example: Some(EXAMPLE.into()), - maximum: Some(MAXIMUM as f64), - minimum: Some(MINIMUM as f64), + maximum: Some(SlotNo::MAXIMUM.0 as f64), + minimum: Some(SlotNo::MINIMUM.0 as f64), ..MetaSchema::ANY } }); @@ -40,9 +36,14 @@ static SCHEMA: LazyLock = LazyLock::new(|| { pub(crate) struct SlotNo(u64); impl SlotNo { + /// Maximum. + pub(crate) const MAXIMUM: SlotNo = SlotNo(u64::MAX / 2); + /// Minimum. + pub(crate) const MINIMUM: SlotNo = SlotNo(0); + /// Is the Slot Number valid? fn is_valid(value: u64) -> bool { - (MINIMUM..=MAXIMUM).contains(&value) + (Self::MINIMUM.0..=Self::MAXIMUM.0).contains(&value) } /// Generic conversion of `Option` to `Option`. From fbf5f3454c5ed71405af158d146fc4c0278a837c Mon Sep 17 00:00:00 2001 From: Alex Pozhylenkov Date: Tue, 1 Apr 2025 11:42:40 +0300 Subject: [PATCH 08/10] fix(cat-gateway): Fix `cardano/assets` endpoint. (#2109) * fix assets endpoint * wip * wip * wip * wip * wip * fix spelling * Update catalyst-gateway/bin/src/service/common/objects/cardano/stake_info.rs Co-authored-by: Steven Johnson * wip --------- Co-authored-by: Steven Johnson --- catalyst-gateway/bin/src/db/types/slot.rs | 8 + .../service/api/cardano/staking/assets_get.rs | 286 +++++++++--------- .../src/service/api/cardano/staking/mod.rs | 7 +- .../common/objects/cardano/stake_info.rs | 20 +- .../service/common/types/cardano/slot_no.rs | 15 +- 5 files changed, 173 insertions(+), 163 deletions(-) diff --git a/catalyst-gateway/bin/src/db/types/slot.rs b/catalyst-gateway/bin/src/db/types/slot.rs index 17e35945e28..b6f01d068b5 100644 --- a/catalyst-gateway/bin/src/db/types/slot.rs +++ b/catalyst-gateway/bin/src/db/types/slot.rs @@ -7,6 +7,8 @@ use scylla::_macro_internal::{ SerializeValue, TypeCheckError, WrittenCellProof, }; +use crate::service::common::types::cardano::slot_no::SlotNo; + /// A `Slot` wrapper that can be stored to and load from a database.\ #[allow(clippy::module_name_repetitions)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -42,6 +44,12 @@ impl From for BigInt { } } +impl From for DbSlot { + fn from(value: SlotNo) -> Self { + Self(value.into()) + } +} + impl SerializeValue for DbSlot { fn serialize<'b>( &self, typ: &ColumnType, writer: CellWriter<'b>, diff --git a/catalyst-gateway/bin/src/service/api/cardano/staking/assets_get.rs b/catalyst-gateway/bin/src/service/api/cardano/staking/assets_get.rs index fd07f5a4f94..d0389c263b6 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/staking/assets_get.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/staking/assets_get.rs @@ -1,10 +1,14 @@ //! Implementation of the GET `../assets` endpoint -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use cardano_blockchain_types::{Slot, StakeAddress, TransactionId, TxnIndex}; -use futures::StreamExt; +use futures::TryStreamExt; use poem_openapi::{payload::Json, ApiResponse}; +use tracing::debug; use crate::{ db::index::{ @@ -23,7 +27,7 @@ use crate::{ service::common::{ objects::cardano::{ network::Network, - stake_info::{FullStakeInfo, StakeInfo, StakedNativeTokenInfo}, + stake_info::{FullStakeInfo, StakeInfo, StakedTxoAssetInfo}, }, responses::WithErrorResponses, types::{ @@ -33,11 +37,9 @@ use crate::{ headers::retry_after::RetryAfterOption, }, }, + settings::Settings, }; -/// A `TxoInfo` by transaction ID map. -type TxosByTxn = HashMap>; - /// Endpoint responses. #[derive(ApiResponse)] pub(crate) enum Responses { @@ -57,10 +59,16 @@ pub(crate) enum Responses { pub(crate) type AllResponses = WithErrorResponses; /// # GET `/staked_ada` -#[allow(clippy::unused_async, clippy::no_effect_underscore_binding)] pub(crate) async fn endpoint( - stake_address: Cip19StakeAddress, _provided_network: Option, slot_num: Option, + stake_address: Cip19StakeAddress, provided_network: Option, slot_num: Option, ) -> AllResponses { + if let Some(provided_network) = provided_network { + if cardano_blockchain_types::Network::from(provided_network) != Settings::cardano_network() + { + return Responses::NotFound.into(); + } + } + let Some(persistent_session) = CassandraSession::get(true) else { tracing::error!("Failed to acquire persistent db session"); return AllResponses::service_unavailable( @@ -77,8 +85,8 @@ pub(crate) async fn endpoint( }; let (persistent_res, volatile_res) = futures::join!( - calculate_stake_info(&persistent_session, stake_address.clone(), slot_num), - calculate_stake_info(&volatile_session, stake_address, slot_num) + calculate_stake_info(persistent_session, stake_address.clone(), slot_num), + calculate_stake_info(volatile_session, stake_address, slot_num) ); let persistent_stake_info = match persistent_res { Ok(stake_info) => stake_info, @@ -114,8 +122,6 @@ struct TxoAssetInfo { struct TxoInfo { /// TXO value. value: num_bigint::BigInt, - /// TXO transaction hash. - txn_hash: TransactionId, /// TXO transaction index within the slot. txn_index: TxnIndex, /// TXO index. @@ -124,8 +130,6 @@ struct TxoInfo { slot_no: Slot, /// Whether the TXO was spent. spent_slot_no: Option, - /// TXO assets. - assets: HashMap, Vec>, } /// Calculate the stake info for a given stake address. @@ -133,189 +137,181 @@ struct TxoInfo { /// This function also updates the spent column if it detects that a TXO was spent /// between lookups. async fn calculate_stake_info( - session: &CassandraSession, stake_address: Cip19StakeAddress, slot_num: Option, + session: Arc, stake_address: Cip19StakeAddress, slot_num: Option, ) -> anyhow::Result> { let address: StakeAddress = stake_address.try_into()?; - let mut txos_by_txn = get_txo_by_txn(session, &address, slot_num).await?; - if txos_by_txn.is_empty() { + let adjusted_slot_num = slot_num.unwrap_or(SlotNo::MAXIMUM); + + let (mut txos, txo_assets) = futures::try_join!( + get_txo(&session, &address, adjusted_slot_num), + get_txo_assets(&session, &address, adjusted_slot_num) + )?; + if txos.is_empty() { return Ok(None); } - check_and_set_spent(session, &mut txos_by_txn).await?; - // TODO: This could be executed in the background, it does not actually matter if it - // succeeds. This is just an optimization step to reduce the need to query spent - // TXO's. - update_spent(session, &address, &txos_by_txn).await?; + let params = update_spent(&session, &address, &mut txos).await?; - let stake_info = build_stake_info(txos_by_txn)?; + // Sets TXOs as spent in the database in the background. + tokio::spawn(async move { + if let Err(err) = UpdateTxoSpentQuery::execute(&session, params).await { + tracing::error!("Failed to update TXO spent info, err: {err}"); + } + }); + + let stake_info = build_stake_info(txos, txo_assets, adjusted_slot_num)?; Ok(Some(stake_info)) } -/// Returns a map of TXO infos by transaction hash for the given stake address. -async fn get_txo_by_txn( - session: &CassandraSession, stake_address: &StakeAddress, slot_num: Option, -) -> anyhow::Result { - let adjusted_slot_num: u64 = slot_num.map_or(u64::MAX, Into::into); +/// `TxoInfo` map type alias +type TxoMap = HashMap<(TransactionId, i16), TxoInfo>; - let mut txo_map = HashMap::new(); - let mut txos_iter = GetTxoByStakeAddressQuery::execute( +/// Returns a map of TXO infos for the given stake address. +async fn get_txo( + session: &CassandraSession, stake_address: &StakeAddress, slot_num: SlotNo, +) -> anyhow::Result { + let txos_stream = GetTxoByStakeAddressQuery::execute( session, - GetTxoByStakeAddressQueryParams::new(stake_address.clone(), adjusted_slot_num.into()), + GetTxoByStakeAddressQueryParams::new(stake_address.clone(), slot_num.into()), ) .await?; - // Aggregate TXO info. - while let Some(row_res) = txos_iter.next().await { - let row = row_res?; - - // Filter out already known spent TXOs. - if row.spent_slot.is_some() { - continue; - } + let txo_map = txos_stream + .map_err(Into::::into) + .try_fold(HashMap::new(), |mut txo_map, row| { + async move { + let key = (row.txn_id.into(), row.txo.into()); + txo_map.insert(key, TxoInfo { + value: row.value, + txn_index: row.txn_index.into(), + txo: row.txo.into(), + slot_no: row.slot_no.into(), + spent_slot_no: row.spent_slot.map(Into::into), + }); + Ok(txo_map) + } + }) + .await?; + Ok(txo_map) +} - let key = (row.slot_no, row.txn_index, row.txo); - txo_map.insert(key, TxoInfo { - value: row.value, - txn_hash: row.txn_id.into(), - txn_index: row.txn_index.into(), - txo: row.txo.into(), - slot_no: row.slot_no.into(), - spent_slot_no: None, - assets: HashMap::new(), - }); - } +/// TXO Assets map type alias +type TxoAssetsMap = HashMap<(Slot, TxnIndex, i16), TxoAssetInfo>; - // Augment TXO info with asset info. - let mut assets_txos_iter = GetAssetsByStakeAddressQuery::execute( +/// Returns a map of txo asset infos for the given stake address. +async fn get_txo_assets( + session: &CassandraSession, stake_address: &StakeAddress, slot_num: SlotNo, +) -> anyhow::Result { + let assets_txos_stream = GetAssetsByStakeAddressQuery::execute( session, - GetAssetsByStakeAddressParams::new(stake_address.clone(), adjusted_slot_num.into()), + GetAssetsByStakeAddressParams::new(stake_address.clone(), slot_num.into()), ) .await?; - while let Some(row_res) = assets_txos_iter.next().await { - let row = row_res?; - - let txo_info_key = (row.slot_no, row.txn_index, row.txo); - let Some(txo_info) = txo_map.get_mut(&txo_info_key) else { - continue; - }; - - let entry = txo_info - .assets - .entry(row.policy_id.clone()) - .or_insert_with(Vec::new); - - match entry.iter_mut().find(|item| item.id == row.policy_id) { - Some(item) => item.amount += row.value, - None => { - entry.push(TxoAssetInfo { + let tokens_map = assets_txos_stream + .map_err(Into::::into) + .try_fold(HashMap::new(), |mut tokens_map, row| { + async move { + let key = (row.slot_no.into(), row.txn_index.into(), row.txo.into()); + tokens_map.insert(key, TxoAssetInfo { id: row.policy_id, name: row.asset_name.into(), amount: row.value, }); - }, - } - } - - let mut txos_by_txn = HashMap::new(); - for txo_info in txo_map.into_values() { - let txn_map = txos_by_txn - .entry(txo_info.txn_hash) - .or_insert(HashMap::new()); - txn_map.insert(txo_info.txo, txo_info); - } - - Ok(txos_by_txn) + Ok(tokens_map) + } + }) + .await?; + Ok(tokens_map) } /// Checks if the given TXOs were spent and mark then as such. -async fn check_and_set_spent( - session: &CassandraSession, txos_by_txn: &mut TxosByTxn, -) -> anyhow::Result<()> { - let txn_hashes = txos_by_txn.keys().copied().collect::>(); +async fn update_spent( + session: &CassandraSession, stake_address: &StakeAddress, txos: &mut TxoMap, +) -> anyhow::Result> { + let txn_hashes = txos + .iter() + .filter(|(_, txo)| txo.spent_slot_no.is_none()) + .map(|((tx_id, _), _)| *tx_id) + .collect::>() + .into_iter() + .collect::>(); + + let mut params = Vec::new(); for chunk in txn_hashes.chunks(100) { - let mut txi_iter = GetTxiByTxnHashesQuery::execute( + let mut txi_stream = GetTxiByTxnHashesQuery::execute( session, GetTxiByTxnHashesQueryParams::new(chunk.to_vec()), ) .await?; - while let Some(row_res) = txi_iter.next().await { - let row = row_res?; - - if let Some(txn_map) = txos_by_txn.get_mut(&row.txn_id.into()) { - if let Some(txo_info) = txn_map.get_mut(&row.txo.into()) { - txo_info.spent_slot_no = Some(row.slot_no.into()); - } - } - } - } - - Ok(()) -} - -/// Sets TXOs as spent in the database if they are marked as spent in the map. -async fn update_spent( - session: &CassandraSession, stake_address: &StakeAddress, txos_by_txn: &TxosByTxn, -) -> anyhow::Result<()> { - let mut params = Vec::new(); - for txn_map in txos_by_txn.values() { - for txo_info in txn_map.values() { - if txo_info.spent_slot_no.is_none() { - continue; - } - - if let Some(spent_slot) = txo_info.spent_slot_no { + while let Some(row) = txi_stream.try_next().await? { + let key = (row.txn_id.into(), row.txo.into()); + if let Some(txo_info) = txos.get_mut(&key) { params.push(UpdateTxoSpentQueryParams { stake_address: stake_address.clone().into(), txn_index: txo_info.txn_index.into(), txo: txo_info.txo.into(), slot_no: txo_info.slot_no.into(), - spent_slot: spent_slot.into(), + spent_slot: row.slot_no, }); + + txo_info.spent_slot_no = Some(row.slot_no.into()); } } } - UpdateTxoSpentQuery::execute(session, params).await?; - - Ok(()) + Ok(params) } /// Builds an instance of [`StakeInfo`] based on the TXOs given. -fn build_stake_info(txos_by_txn: TxosByTxn) -> anyhow::Result { +fn build_stake_info( + txos: TxoMap, mut tokens: TxoAssetsMap, slot_num: SlotNo, +) -> anyhow::Result { + let slot_num = slot_num.into(); let mut stake_info = StakeInfo::default(); - for txn_map in txos_by_txn.into_values() { - for txo_info in txn_map.into_values() { - if txo_info.spent_slot_no.is_none() { - let value = u64::try_from(txo_info.value)?; - stake_info.ada_amount = stake_info - .ada_amount - .checked_add(value) - .ok_or_else(|| { - anyhow::anyhow!( - "Total stake amount overflow: {} + {value}", - stake_info.ada_amount - ) - })? - .into(); - - for asset in txo_info.assets.into_values().flatten() { - stake_info.native_tokens.push(StakedNativeTokenInfo { - policy_hash: asset.id.try_into()?, - asset_name: asset.name, - amount: asset.amount.try_into()?, - }); - } + for txo_info in txos.into_values() { + // Filter out spent TXOs. + if let Some(spent_slot) = txo_info.spent_slot_no { + if spent_slot <= slot_num { + continue; + } + } - let slot_no = txo_info.slot_no.into(); - if stake_info.slot_number < slot_no { - stake_info.slot_number = slot_no; - } + let value = u64::try_from(txo_info.value)?; + stake_info.ada_amount = stake_info + .ada_amount + .checked_add(value) + .ok_or_else(|| { + anyhow::anyhow!( + "Total stake amount overflow: {} + {value}", + stake_info.ada_amount + ) + })? + .into(); + + let key = (txo_info.slot_no, txo_info.txn_index, txo_info.txo); + if let Some(native_token) = tokens.remove(&key) { + match native_token.amount.try_into() { + Ok(amount) => { + stake_info.assets.push(StakedTxoAssetInfo { + policy_hash: native_token.id.try_into()?, + asset_name: native_token.name, + amount, + }); + }, + Err(e) => { + debug!("Invalid TXO Asset for {key:?}: {e}"); + }, } } + + let slot_no = txo_info.slot_no.into(); + if stake_info.slot_number < slot_no { + stake_info.slot_number = slot_no; + } } Ok(stake_info) diff --git a/catalyst-gateway/bin/src/service/api/cardano/staking/mod.rs b/catalyst-gateway/bin/src/service/api/cardano/staking/mod.rs index fe5cba05017..397e14f88f1 100644 --- a/catalyst-gateway/bin/src/service/api/cardano/staking/mod.rs +++ b/catalyst-gateway/bin/src/service/api/cardano/staking/mod.rs @@ -51,6 +51,11 @@ impl Api { /// No Authorization required, but Token permitted. _auth: NoneOrRBAC, ) -> assets_get::AllResponses { - assets_get::endpoint(stake_address.0, network.0, SlotNo::into_option(asat.0)).await + Box::pin(assets_get::endpoint( + stake_address.0, + network.0, + SlotNo::into_option(asat.0), + )) + .await } } diff --git a/catalyst-gateway/bin/src/service/common/objects/cardano/stake_info.rs b/catalyst-gateway/bin/src/service/common/objects/cardano/stake_info.rs index e18cf53472b..f9d62908799 100644 --- a/catalyst-gateway/bin/src/service/common/objects/cardano/stake_info.rs +++ b/catalyst-gateway/bin/src/service/common/objects/cardano/stake_info.rs @@ -14,10 +14,10 @@ use crate::service::common::types::{ }, }; -/// User's staked native token info. +/// User's staked txo asset info. #[derive(Object, Debug, Clone)] #[oai(example)] -pub(crate) struct StakedNativeTokenInfo { +pub(crate) struct StakedTxoAssetInfo { /// Token policy hash. pub(crate) policy_hash: HexEncodedHash28, /// Token policies Asset Name. @@ -26,7 +26,7 @@ pub(crate) struct StakedNativeTokenInfo { pub(crate) amount: AssetValue, } -impl Example for StakedNativeTokenInfo { +impl Example for StakedTxoAssetInfo { fn example() -> Self { Self { policy_hash: Example::example(), @@ -38,17 +38,17 @@ impl Example for StakedNativeTokenInfo { // List of User's Staked Native Token Info impl_array_types!( - StakedNativeTokenInfoList, - StakedNativeTokenInfo, + StakedAssetInfoList, + StakedTxoAssetInfo, Some(poem_openapi::registry::MetaSchema { example: Self::example().to_json(), max_items: Some(1000), - items: Some(Box::new(StakedNativeTokenInfo::schema_ref())), + items: Some(Box::new(StakedTxoAssetInfo::schema_ref())), ..poem_openapi::registry::MetaSchema::ANY }) ); -impl Example for StakedNativeTokenInfoList { +impl Example for StakedAssetInfoList { fn example() -> Self { Self(vec![Example::example()]) } @@ -64,8 +64,8 @@ pub(crate) struct StakeInfo { /// Block's slot number which contains the latest unspent UTXO. pub(crate) slot_number: SlotNo, - /// Native token infos. - pub(crate) native_tokens: StakedNativeTokenInfoList, + /// TXO assets infos. + pub(crate) assets: StakedAssetInfoList, } impl Example for StakeInfo { @@ -73,7 +73,7 @@ impl Example for StakeInfo { Self { slot_number: SlotNo::example(), ada_amount: AdaValue::example(), - native_tokens: Vec::new().into(), + assets: Vec::new().into(), } } } diff --git a/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs b/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs index 482747ef362..393753a7033 100644 --- a/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs +++ b/catalyst-gateway/bin/src/service/common/types/cardano/slot_no.rs @@ -17,10 +17,6 @@ const TITLE: &str = "Cardano Blockchain Slot Number"; const DESCRIPTION: &str = "The Slot Number of a Cardano Block on the chain."; /// Example. pub(crate) const EXAMPLE: u64 = 1_234_567; -/// Minimum. -const MINIMUM: u64 = 0; -/// Maximum. -const MAXIMUM: u64 = u64::MAX / 2; /// Schema. #[allow(clippy::cast_precision_loss)] @@ -29,8 +25,8 @@ static SCHEMA: LazyLock = LazyLock::new(|| { title: Some(TITLE.to_owned()), description: Some(DESCRIPTION), example: Some(EXAMPLE.into()), - maximum: Some(MAXIMUM as f64), - minimum: Some(MINIMUM as f64), + maximum: Some(SlotNo::MAXIMUM.0 as f64), + minimum: Some(SlotNo::MINIMUM.0 as f64), ..MetaSchema::ANY } }); @@ -40,9 +36,14 @@ static SCHEMA: LazyLock = LazyLock::new(|| { pub(crate) struct SlotNo(u64); impl SlotNo { + /// Maximum. + pub(crate) const MAXIMUM: SlotNo = SlotNo(u64::MAX / 2); + /// Minimum. + pub(crate) const MINIMUM: SlotNo = SlotNo(0); + /// Is the Slot Number valid? fn is_valid(value: u64) -> bool { - (MINIMUM..=MAXIMUM).contains(&value) + (Self::MINIMUM.0..=Self::MAXIMUM.0).contains(&value) } /// Generic conversion of `Option` to `Option`. From 3f3f7d997a1bf1f4df06b97062832fddf2215a44 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 1 Apr 2025 12:02:12 +0300 Subject: [PATCH 09/10] wip --- catalyst-gateway/bin/Cargo.toml | 2 -- catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/catalyst-gateway/bin/Cargo.toml b/catalyst-gateway/bin/Cargo.toml index 75d3423595b..d7809b41bb6 100644 --- a/catalyst-gateway/bin/Cargo.toml +++ b/catalyst-gateway/bin/Cargo.toml @@ -85,7 +85,6 @@ prometheus = "0.13.4" rust-embed = "8.5.0" num-traits = "0.2.19" base64 = "0.22.1" -dashmap = { version = "6.1.0", features = ["rayon"] } jsonschema = "0.26.1" bech32 = "0.11.0" const_format = "0.2.33" @@ -96,7 +95,6 @@ mime = "0.3.17" stats_alloc = "0.1.10" memory-stats = "1.0.0" derive_more = { version = "2.0.1", default-features = false, features = ["from", "into"] } -rayon = "1.10" # Its a transitive dependency of the "poem-openapi" crate, # but its breaks API after version "5.1.8". diff --git a/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs b/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs index 537da440483..cea85561b2c 100644 --- a/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs +++ b/catalyst-gateway/bin/src/db/index/queries/registrations/mod.rs @@ -3,6 +3,6 @@ pub(crate) mod get_all_invalids; pub(crate) mod get_all_registrations; pub(crate) mod get_from_stake_pk; -pub(crate) mod get_stake_pk_from_stake_addr; pub(crate) mod get_from_vote_key; pub(crate) mod get_invalid; +pub(crate) mod get_stake_pk_from_stake_addr; From c827a55d7fe1ecb6274025ef4dfb59732815b1bd Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Tue, 1 Apr 2025 13:18:10 +0300 Subject: [PATCH 10/10] wip --- .../queries/cql/get_invalid_registration_w_stake_addr.cql | 8 ++++++-- .../index/queries/cql/get_registrations_w_stake_addr.cql | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/catalyst-gateway/bin/src/db/index/queries/cql/get_invalid_registration_w_stake_addr.cql b/catalyst-gateway/bin/src/db/index/queries/cql/get_invalid_registration_w_stake_addr.cql index e69dcdc7950..dcee70e869c 100644 --- a/catalyst-gateway/bin/src/db/index/queries/cql/get_invalid_registration_w_stake_addr.cql +++ b/catalyst-gateway/bin/src/db/index/queries/cql/get_invalid_registration_w_stake_addr.cql @@ -1,4 +1,8 @@ -SELECT problem_report, +SELECT nonce, + raw_nonce, + slot_no, + txn_index, + problem_report, stake_public_key, vote_key, payment_address, @@ -6,4 +10,4 @@ SELECT problem_report, cip36 FROM cip36_registration_invalid WHERE stake_public_key = :stake_public_key - AND slot_no >= :slot_no \ No newline at end of file + AND slot_no <= :slot_no \ No newline at end of file diff --git a/catalyst-gateway/bin/src/db/index/queries/cql/get_registrations_w_stake_addr.cql b/catalyst-gateway/bin/src/db/index/queries/cql/get_registrations_w_stake_addr.cql index b8139325ba4..8f1d72e86d9 100644 --- a/catalyst-gateway/bin/src/db/index/queries/cql/get_registrations_w_stake_addr.cql +++ b/catalyst-gateway/bin/src/db/index/queries/cql/get_registrations_w_stake_addr.cql @@ -1,9 +1,11 @@ SELECT nonce, slot_no, txn_index, + raw_nonce, vote_key, payment_address, is_payable, cip36 FROM cip36_registration -WHERE stake_public_key = :stake_public_key \ No newline at end of file +WHERE stake_public_key = :stake_public_key + AND slot_no <= :slot_no \ No newline at end of file