diff --git a/catalyst-gateway/bin/Cargo.toml b/catalyst-gateway/bin/Cargo.toml index 100c2da663d6..1b050b8582b0 100644 --- a/catalyst-gateway/bin/Cargo.toml +++ b/catalyst-gateway/bin/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] cardano-chain-follower = { version = "0.0.17", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cardano-chain-follower/v0.0.17" } -rbac-registration = { version = "0.0.13", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "rbac-registration/v0.0.13" } +rbac-registration = { version = "0.0.13", git = "https://github.com/input-output-hk/catalyst-libs.git", rev = "95af33a685341bcb69993677c361253f2b9c404e" } catalyst-signed-doc = { version = "0.0.9", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "catalyst-signed-doc/v0.0.9" } catalyst-signed-doc-v1 = { package = "catalyst-signed-doc", version = "0.0.4", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "catalyst-signed-doc/v.0.0.4" } c509-certificate = { version = "0.0.3", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "c509-certificate-v0.0.3" } diff --git a/catalyst-gateway/bin/src/db/index/block/rbac509/mod.rs b/catalyst-gateway/bin/src/db/index/block/rbac509/mod.rs index b4760d2d0283..a2079da7f847 100644 --- a/catalyst-gateway/bin/src/db/index/block/rbac509/mod.rs +++ b/catalyst-gateway/bin/src/db/index/block/rbac509/mod.rs @@ -13,21 +13,24 @@ use std::{ use anyhow::{Context, Result}; use cardano_chain_follower::{hashes::TransactionId, MultiEraBlock, Slot, TxnIndex}; -use rbac_registration::cardano::cip509::Cip509; +use rbac_registration::{ + cardano::cip509::Cip509, + registration::cardano::{RbacValidationError, RegistrationChain}, +}; use scylla::client::session::Session; use tokio::sync::watch; use tracing::{debug, error}; use crate::{ db::index::{ - queries::{FallibleQueryTasks, PreparedQuery, SizedBatch}, + queries::{ + rbac::get_catalyst_id_from_stake_address::cache_stake_address, FallibleQueryTasks, + PreparedQuery, SizedBatch, + }, session::CassandraSession, }, metrics::caches::rbac::{inc_index_sync, inc_invalid_rbac_reg_count}, - rbac::{ - validate_rbac_registration, RbacBlockIndexingContext, RbacValidationError, - RbacValidationSuccess, - }, + rbac::{cache_persistent_rbac_chain, RbacBlockIndexingContext}, settings::cassandra_db::EnvVars, }; @@ -123,24 +126,80 @@ impl Rbac509InsertQuery { wait_for_previous_blocks(pending_blocks, our_end, block.slot()).await?; let previous_transaction = cip509.previous_transaction(); - // `Box::pin` is used here because of the future size (`clippy::large_futures` lint). - match Box::pin(validate_rbac_registration( - cip509, - block.is_immutable(), - context, - )) - .await - { + + // Before it is consumed + let txn_id = cip509.txn_hash(); + let origin = cip509.origin().clone(); + let purpose = cip509.purpose(); + let previous_txn = cip509.previous_transaction(); + + context.set_persistent(block.is_immutable()); + + match RegistrationChain::update_from_previous_txn(cip509, context).await { // Write updates to the database. There can be multiple updates in one registration // because a new chain can take ownership of stake addresses of the existing chains and // in that case we want to record changes to all those chains as well as the new one. - Ok(RbacValidationSuccess { - catalyst_id, - stake_addresses, - public_keys, - modified_chains, - purpose, - }) => { + Ok((chain, modified_chains)) => { + let catalyst_id = chain.catalyst_id(); + let stake_addresses = chain.stake_addresses(); + let public_keys: HashSet<_> = chain + .role_data_history() + .keys() + .filter_map(|role| { + chain + .get_latest_signing_pk_for_role(role) + .map(|(key, _)| key) + }) + .collect(); + + if let Some(previous_txn) = previous_txn { + context.insert_transaction(txn_id, catalyst_id.clone()); + context.insert_addresses(stake_addresses.clone(), catalyst_id); + context.insert_public_keys(public_keys.clone(), catalyst_id); + context.insert_registration( + catalyst_id.clone(), + txn_id, + origin.point().slot_or_default(), + origin.txn_index(), + Some(previous_txn), + // Only a new chain can remove stake addresses from an existing one. + HashSet::new(), + ); + + if block.is_immutable() { + cache_persistent_rbac_chain(catalyst_id.clone(), chain.clone()); + } + } else { + context.insert_transaction(chain.current_tx_id_hash(), catalyst_id.clone()); + // This will also update the addresses that are already present in the context + // if they were reassigned to the new chain. + context.insert_addresses(stake_addresses.clone(), catalyst_id); + context.insert_public_keys(public_keys.clone(), catalyst_id); + context.insert_registration( + catalyst_id.clone(), + chain.current_tx_id_hash(), + chain.current_point().slot_or_default(), + chain.current_txn_index(), + // No previous transaction for the root registration. + None, + // This chain has just been created, so no addresses have been removed from + // it. + HashSet::new(), + ); + + // This cache must be updated because these addresses previously belonged to + // other chains. + for (catalyst_id, addresses) in &modified_chains { + for address in addresses { + cache_stake_address( + block.is_immutable(), + address.clone(), + catalyst_id.clone(), + ); + } + } + } + // Record the transaction identifier (hash) of a new registration. self.catalyst_id_for_txn_id .push(insert_catalyst_id_for_txn_id::Params::new( @@ -171,7 +230,7 @@ impl Rbac509InsertQuery { } // Update the chain this registration belongs to. self.registrations.push(insert_rbac509::Params::new( - catalyst_id, + catalyst_id.clone(), txn_hash, slot, index, diff --git a/catalyst-gateway/bin/src/rbac/indexing_context.rs b/catalyst-gateway/bin/src/rbac/indexing_context.rs index 57f17088813d..8a47c23b8e67 100644 --- a/catalyst-gateway/bin/src/rbac/indexing_context.rs +++ b/catalyst-gateway/bin/src/rbac/indexing_context.rs @@ -2,11 +2,23 @@ use std::collections::{HashMap, HashSet}; +use anyhow::Context; use cardano_chain_follower::{hashes::TransactionId, Slot, StakeAddress, TxnIndex}; use catalyst_types::catalyst_id::CatalystId; use ed25519_dalek::VerifyingKey; +use futures::StreamExt; +use rbac_registration::providers::RbacRegistrationProvider; -use crate::db::index::queries::rbac::get_rbac_registrations::Query as RbacQuery; +use crate::{ + db::index::{ + queries::rbac::get_rbac_registrations::Query as RbacQuery, session::CassandraSession, + }, + rbac::{ + chains_cache::cached_persistent_rbac_chain, + get_chain::{apply_regs, build_rbac_chain, persistent_rbac_chain}, + latest_rbac_chain, + }, +}; /// A RBAC context used during indexing. /// @@ -28,6 +40,8 @@ pub struct RbacBlockIndexingContext { /// A map containing pending data that will be written in the `rbac_registration` /// table. registrations: HashMap>, + /// Indicates whether the provider should fetch data from persistent storage. + is_persistent: bool, } impl RbacBlockIndexingContext { @@ -43,6 +57,7 @@ impl RbacBlockIndexingContext { addresses, public_keys, registrations, + is_persistent: false, } } @@ -159,4 +174,169 @@ impl RbacBlockIndexingContext { ) -> Option<&[RbacQuery]> { self.registrations.get(id).map(Vec::as_slice) } + + /// Sets `is_persistent`. + pub fn set_persistent( + &mut self, + value: bool, + ) { + self.is_persistent = value; + } +} + +impl RbacRegistrationProvider for RbacBlockIndexingContext { + async fn chain( + &self, + id: CatalystId, + ) -> anyhow::Result> { + let chain = if self.is_persistent { + persistent_rbac_chain(&id).await? + } else { + latest_rbac_chain(&id).await?.map(|i| i.chain) + }; + + // Apply additional registrations from context if any. + if let Some(regs) = self.find_registrations(&id) { + let regs = regs.iter().cloned(); + match chain { + Some(c) => apply_regs(c, regs).await.map(Some), + None => build_rbac_chain(regs).await, + } + } else { + Ok(chain) + } + } + + async fn catalyst_id_from_txn_id( + &self, + txn_id: TransactionId, + ) -> anyhow::Result> { + use crate::db::index::queries::rbac::get_catalyst_id_from_transaction_id::Query; + + // Check the context first. + if let Some(catalyst_id) = self.find_transaction(&txn_id) { + return Ok(Some(catalyst_id.to_owned())); + } + + // Then try to find in the persistent database. + let session = + CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; + if let Some(id) = Query::get(&session, txn_id).await? { + return Ok(Some(id)); + } + + // Conditionally check the volatile database. + if !self.is_persistent { + let session = + CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; + return Query::get(&session, txn_id).await; + } + + Ok(None) + } + + async fn catalyst_id_from_stake_address( + &self, + address: &StakeAddress, + ) -> anyhow::Result> { + use crate::db::index::queries::rbac::get_catalyst_id_from_stake_address::Query; + + // Check the context first. + if let Some(catalyst_id) = self.find_address(address) { + return Ok(Some(catalyst_id.to_owned())); + } + + // Then try to find in the persistent database. + let session = + CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; + if let Some(id) = Query::latest(&session, address).await? { + return Ok(Some(id)); + } + + // Conditionally check the volatile database. + if !self.is_persistent { + let session = + CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; + return Query::latest(&session, address).await; + } + + Ok(None) + } + + async fn catalyst_id_from_public_key( + &self, + key: VerifyingKey, + ) -> anyhow::Result> { + use crate::db::index::queries::rbac::get_catalyst_id_from_public_key::Query; + + // Check the context first. + if let Some(catalyst_id) = self.find_public_key(&key) { + return Ok(Some(catalyst_id.to_owned())); + } + + // Then try to find in the persistent database. + let session = + CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; + if let Some(id) = Query::get(&session, key).await? { + return Ok(Some(id)); + } + + // Conditionally check the volatile database. + if !self.is_persistent { + let session = + CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; + return Query::get(&session, key).await; + } + + Ok(None) + } + + async fn is_chain_known( + &self, + id: CatalystId, + ) -> anyhow::Result { + if self.find_registrations(&id).is_some() { + return Ok(true); + } + + let session = + CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; + + // We only cache persistent chains, so it is ok to check the cache regardless of the + // `is_persistent` parameter value. + if cached_persistent_rbac_chain(&session, &id).is_some() { + return Ok(true); + } + + if is_cat_id_known(&session, &id).await? { + return Ok(true); + } + + // Conditionally check the volatile database. + if !self.is_persistent { + let session = + CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; + if is_cat_id_known(&session, &id).await? { + return Ok(true); + } + } + + Ok(false) + } +} + +/// Returns `true` if there is at least one registration with the given Catalyst ID. +async fn is_cat_id_known( + session: &CassandraSession, + id: &CatalystId, +) -> anyhow::Result { + use crate::db::index::queries::rbac::get_rbac_registrations::{Query, QueryParams}; + + Ok(Query::execute(session, QueryParams { + catalyst_id: id.clone().into(), + }) + .await? + .next() + .await + .is_some()) } diff --git a/catalyst-gateway/bin/src/rbac/mod.rs b/catalyst-gateway/bin/src/rbac/mod.rs index f86384174f0e..1a3072cc413e 100644 --- a/catalyst-gateway/bin/src/rbac/mod.rs +++ b/catalyst-gateway/bin/src/rbac/mod.rs @@ -4,11 +4,8 @@ mod chain_info; mod chains_cache; mod get_chain; mod indexing_context; -mod validation; -mod validation_result; pub use chain_info::ChainInfo; +pub use chains_cache::cache_persistent_rbac_chain; pub use get_chain::latest_rbac_chain; pub use indexing_context::RbacBlockIndexingContext; -pub use validation::validate_rbac_registration; -pub use validation_result::{RbacValidationError, RbacValidationResult, RbacValidationSuccess}; diff --git a/catalyst-gateway/bin/src/rbac/validation.rs b/catalyst-gateway/bin/src/rbac/validation.rs deleted file mode 100644 index bfcee1f097b1..000000000000 --- a/catalyst-gateway/bin/src/rbac/validation.rs +++ /dev/null @@ -1,462 +0,0 @@ -//! Utilities for RBAC registrations validation. - -use std::collections::{HashMap, HashSet}; - -use anyhow::{Context, Result}; -use cardano_chain_follower::{hashes::TransactionId, StakeAddress}; -use catalyst_types::{ - catalyst_id::{role_index::RoleId, CatalystId}, - problem_report::ProblemReport, -}; -use ed25519_dalek::VerifyingKey; -use futures::StreamExt; -use rbac_registration::{ - cardano::cip509::{Cip0134UriSet, Cip509}, - registration::cardano::RegistrationChain, -}; - -use crate::{ - db::index::{ - queries::rbac::get_catalyst_id_from_stake_address::cache_stake_address, - session::CassandraSession, - }, - rbac::{ - chains_cache::{cache_persistent_rbac_chain, cached_persistent_rbac_chain}, - get_chain::{apply_regs, build_rbac_chain, persistent_rbac_chain}, - latest_rbac_chain, RbacBlockIndexingContext, RbacValidationError, RbacValidationResult, - RbacValidationSuccess, - }, -}; - -/// Validates a new registration by either starting a new chain or adding it to the -/// existing one. -/// -/// In case of failure a problem report from the given registration is updated and -/// returned. -pub async fn validate_rbac_registration( - reg: Cip509, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> RbacValidationResult { - match reg.previous_transaction() { - // `Box::pin` is used here because of the future size (`clippy::large_futures` lint). - Some(previous_txn) => { - Box::pin(update_chain(reg, previous_txn, is_persistent, context)).await - }, - None => Box::pin(start_new_chain(reg, is_persistent, context)).await, - } -} - -/// Tries to update an existing RBAC chain. -async fn update_chain( - reg: Cip509, - previous_txn: TransactionId, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> RbacValidationResult { - let purpose = reg.purpose(); - let report = reg.report().to_owned(); - - // Find a chain this registration belongs to. - let Some(catalyst_id) = catalyst_id_from_txn_id(previous_txn, is_persistent, context).await? - else { - // We are unable to determine a Catalyst ID, so there is no sense to update the problem - // report because we would be unable to store this registration anyway. - return Err(RbacValidationError::UnknownCatalystId); - }; - let chain = chain(&catalyst_id, is_persistent, context).await? - .context("{catalyst_id} is present in 'catalyst_id_for_txn_id' table, but not in 'rbac_registration'")?; - - // Check that addresses from the new registration aren't used in other chains. - let previous_addresses = chain.stake_addresses(); - let reg_addresses = cip509_stake_addresses(®); - let new_addresses: Vec<_> = reg_addresses.difference(&previous_addresses).collect(); - for address in &new_addresses { - match catalyst_id_from_stake_address(address, is_persistent, context).await? { - None => { - // All good: the address wasn't used before. - }, - Some(_) => { - report.functional_validation( - &format!("{address} stake addresses is already used"), - "It isn't allowed to use same stake address in multiple registration chains", - ); - }, - } - } - - // Store values before consuming the registration. - let txn_id = reg.txn_hash(); - let stake_addresses = cip509_stake_addresses(®); - let origin = reg.origin().to_owned(); - - // Try to add a new registration to the chain. - let new_chain = chain.update(reg).ok_or_else(|| { - RbacValidationError::InvalidRegistration { - catalyst_id: catalyst_id.clone(), - purpose, - report: report.clone(), - } - })?; - - // Check that new public keys aren't used by other chains. - let public_keys = validate_public_keys(&new_chain, is_persistent, &report, context).await?; - - // Return an error if any issues were recorded in the report. - if report.is_problematic() { - return Err(RbacValidationError::InvalidRegistration { - catalyst_id, - purpose, - report, - }); - } - - // Everything is fine: update the context. - context.insert_transaction(txn_id, catalyst_id.clone()); - context.insert_addresses(stake_addresses.clone(), &catalyst_id); - context.insert_public_keys(public_keys.clone(), &catalyst_id); - context.insert_registration( - catalyst_id.clone(), - txn_id, - origin.point().slot_or_default(), - origin.txn_index(), - Some(previous_txn), - // Only a new chain can remove stake addresses from an existing one. - HashSet::new(), - ); - - if is_persistent { - cache_persistent_rbac_chain(catalyst_id.clone(), new_chain); - } - - Ok(RbacValidationSuccess { - catalyst_id, - stake_addresses, - public_keys, - // Only new chains can take ownership of stake addresses of existing chains, so in this case - // other chains aren't affected. - modified_chains: Vec::new(), - purpose, - }) -} - -/// Tries to start a new RBAC chain. -async fn start_new_chain( - reg: Cip509, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> RbacValidationResult { - let catalyst_id = reg.catalyst_id().map(CatalystId::as_short_id); - let purpose = reg.purpose(); - let report = reg.report().to_owned(); - - // Try to start a new chain. - let new_chain = RegistrationChain::new(reg).ok_or_else(|| { - if let Some(catalyst_id) = catalyst_id { - RbacValidationError::InvalidRegistration { - catalyst_id, - purpose, - report: report.clone(), - } - } else { - RbacValidationError::UnknownCatalystId - } - })?; - - // Verify that a Catalyst ID of this chain is unique. - let catalyst_id = new_chain.catalyst_id().as_short_id(); - if is_chain_known(&catalyst_id, is_persistent, context).await? { - report.functional_validation( - &format!("{catalyst_id} is already used"), - "It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains", - ); - return Err(RbacValidationError::InvalidRegistration { - catalyst_id, - purpose, - report, - }); - } - - // Validate stake addresses. - let new_addresses = new_chain.stake_addresses(); - let mut updated_chains: HashMap<_, HashSet> = HashMap::new(); - for address in &new_addresses { - if let Some(id) = catalyst_id_from_stake_address(address, is_persistent, context).await? { - // If an address is used in existing chain then a new chain must have different role 0 - // signing key. - let previous_chain = chain(&id, is_persistent, context) - .await? - .context("{id} is present in 'catalyst_id_for_stake_address', but not in 'rbac_registration'")?; - if previous_chain.get_latest_signing_pk_for_role(&RoleId::Role0) - == new_chain.get_latest_signing_pk_for_role(&RoleId::Role0) - { - report.functional_validation( - &format!("A new registration ({catalyst_id}) uses the same public key as the previous one ({})", - previous_chain.catalyst_id().as_short_id() - ), - "It is only allowed to override the existing chain by using different public key", - ); - } else { - // The new root registration "takes" an address(es) from the existing chain, so that - // chain needs to be updated. - updated_chains - .entry(id) - .and_modify(|e| { - e.insert(address.clone()); - }) - .or_insert([address.clone()].into_iter().collect()); - } - } - } - - // Check that new public keys aren't used by other chains. - let public_keys = validate_public_keys(&new_chain, is_persistent, &report, context).await?; - - if report.is_problematic() { - return Err(RbacValidationError::InvalidRegistration { - catalyst_id, - purpose, - report, - }); - } - - // Everything is fine: update the context. - context.insert_transaction(new_chain.current_tx_id_hash(), catalyst_id.clone()); - // This will also update the addresses that are already present in the context if they - // were reassigned to the new chain. - context.insert_addresses(new_addresses.clone(), &catalyst_id); - context.insert_public_keys(public_keys.clone(), &catalyst_id); - context.insert_registration( - catalyst_id.clone(), - new_chain.current_tx_id_hash(), - new_chain.current_point().slot_or_default(), - new_chain.current_txn_index(), - // No previous transaction for the root registration. - None, - // This chain has just been created, so no addresses have been removed from it. - HashSet::new(), - ); - - // This cache must be updated because these addresses previously belonged to other chains. - for (catalyst_id, addresses) in &updated_chains { - for address in addresses { - cache_stake_address(is_persistent, address.clone(), catalyst_id.clone()); - } - } - - Ok(RbacValidationSuccess { - catalyst_id, - stake_addresses: new_addresses, - public_keys, - modified_chains: updated_chains.into_iter().collect(), - purpose, - }) -} - -/// Returns a Catalyst ID corresponding to the given transaction hash. -async fn catalyst_id_from_txn_id( - txn_id: TransactionId, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> Result> { - use crate::db::index::queries::rbac::get_catalyst_id_from_transaction_id::Query; - - // Check the context first. - if let Some(catalyst_id) = context.find_transaction(&txn_id) { - return Ok(Some(catalyst_id.to_owned())); - } - - // Then try to find in the persistent database. - let session = - CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; - if let Some(id) = Query::get(&session, txn_id).await? { - return Ok(Some(id)); - } - - // Conditionally check the volatile database. - if !is_persistent { - let session = - CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; - return Query::get(&session, txn_id).await; - } - - Ok(None) -} - -/// Returns either persistent or "latest" (persistent + volatile) registration chain for -/// the given Catalyst ID. -async fn chain( - id: &CatalystId, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> Result> { - let chain = if is_persistent { - persistent_rbac_chain(id).await? - } else { - latest_rbac_chain(id).await?.map(|i| i.chain) - }; - - // Apply additional registrations from context if any. - if let Some(regs) = context.find_registrations(id) { - let regs = regs.iter().cloned(); - match chain { - Some(c) => apply_regs(c, regs).await.map(Some), - None => build_rbac_chain(regs).await, - } - } else { - Ok(chain) - } -} - -/// Returns a Catalyst ID corresponding to the given stake address. -async fn catalyst_id_from_stake_address( - address: &StakeAddress, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> Result> { - use crate::db::index::queries::rbac::get_catalyst_id_from_stake_address::Query; - - // Check the context first. - if let Some(catalyst_id) = context.find_address(address) { - return Ok(Some(catalyst_id.to_owned())); - } - - // Then try to find in the persistent database. - let session = - CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; - if let Some(id) = Query::latest(&session, address).await? { - return Ok(Some(id)); - } - - // Conditionally check the volatile database. - if !is_persistent { - let session = - CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; - return Query::latest(&session, address).await; - } - - Ok(None) -} - -/// Checks that a new registration doesn't contain a signing key that was used by any -/// other chain. Returns a list of public keys in the registration. -async fn validate_public_keys( - chain: &RegistrationChain, - is_persistent: bool, - report: &ProblemReport, - context: &mut RbacBlockIndexingContext, -) -> Result> { - let mut keys = HashSet::new(); - - let roles: Vec<_> = chain.role_data_history().keys().collect(); - let catalyst_id = chain.catalyst_id().as_short_id(); - - for role in roles { - if let Some((key, _)) = chain.get_latest_signing_pk_for_role(role) { - keys.insert(key); - if let Some(previous) = catalyst_id_from_public_key(key, is_persistent, context).await? - { - if previous != catalyst_id { - report.functional_validation( - &format!("An update to {catalyst_id} registration chain uses the same public key ({key:?}) as {previous} chain"), - "It isn't allowed to use role 0 signing (certificate subject public) key in different chains", - ); - } - } - } - } - - Ok(keys) -} - -/// Returns a Catalyst ID corresponding to the given public key. -async fn catalyst_id_from_public_key( - key: VerifyingKey, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> Result> { - use crate::db::index::queries::rbac::get_catalyst_id_from_public_key::Query; - - // Check the context first. - if let Some(catalyst_id) = context.find_public_key(&key) { - return Ok(Some(catalyst_id.to_owned())); - } - - // Then try to find in the persistent database. - let session = - CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; - if let Some(id) = Query::get(&session, key).await? { - return Ok(Some(id)); - } - - // Conditionally check the volatile database. - if !is_persistent { - let session = - CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; - return Query::get(&session, key).await; - } - - Ok(None) -} - -/// Returns `true` if a chain with the given Catalyst ID already exists. -/// -/// This function behaves in the same way as `latest_rbac_chain(...).is_some()` but the -/// implementation is more optimized because we don't need to build the whole chain. -pub async fn is_chain_known( - id: &CatalystId, - is_persistent: bool, - context: &mut RbacBlockIndexingContext, -) -> Result { - if context.find_registrations(id).is_some() { - return Ok(true); - } - - let session = - CassandraSession::get(true).context("Failed to get Cassandra persistent session")?; - - // We only cache persistent chains, so it is ok to check the cache regardless of the - // `is_persistent` parameter value. - if cached_persistent_rbac_chain(&session, id).is_some() { - return Ok(true); - } - - if is_cat_id_known(&session, id).await? { - return Ok(true); - } - - // Conditionally check the volatile database. - if !is_persistent { - let session = - CassandraSession::get(false).context("Failed to get Cassandra volatile session")?; - if is_cat_id_known(&session, id).await? { - return Ok(true); - } - } - - Ok(false) -} - -/// Returns `true` if there is at least one registration with the given Catalyst ID. -async fn is_cat_id_known( - session: &CassandraSession, - id: &CatalystId, -) -> Result { - use crate::db::index::queries::rbac::get_rbac_registrations::{Query, QueryParams}; - - Ok(Query::execute(session, QueryParams { - catalyst_id: id.clone().into(), - }) - .await? - .next() - .await - .is_some()) -} - -/// Returns a set of stake addresses in the given registration. -fn cip509_stake_addresses(cip509: &Cip509) -> HashSet { - cip509 - .certificate_uris() - .map(Cip0134UriSet::stake_addresses) - .unwrap_or_default() -} diff --git a/catalyst-gateway/bin/src/rbac/validation_result.rs b/catalyst-gateway/bin/src/rbac/validation_result.rs deleted file mode 100644 index 18061b342566..000000000000 --- a/catalyst-gateway/bin/src/rbac/validation_result.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Types related to validation of new RBAC registrations. - -use std::collections::HashSet; - -use cardano_chain_follower::StakeAddress; -use catalyst_types::{catalyst_id::CatalystId, problem_report::ProblemReport, uuid::UuidV4}; -use ed25519_dalek::VerifyingKey; - -/// A return value of the `validate_rbac_registration` method. -pub type RbacValidationResult = Result; - -/// A value returned from the `validate_rbac_registration` on happy path. -/// -/// It contains information for updating `rbac_registration`, -/// `catalyst_id_for_public_key`, `catalyst_id_for_stake_address` and -/// `catalyst_id_for_txn_id` tables. -pub struct RbacValidationSuccess { - /// A Catalyst ID of the chain this registration belongs to. - pub catalyst_id: CatalystId, - /// A list of stake addresses that were added to the chain. - pub stake_addresses: HashSet, - /// A list of role public keys used in this registration. - pub public_keys: HashSet, - /// A list of updates to other chains containing Catalyst IDs and removed stake - /// addresses. - /// - /// A new RBAC registration can take ownership of stake addresses of other chains. - pub modified_chains: Vec<(CatalystId, HashSet)>, - /// A registration purpose. - pub purpose: Option, -} - -/// An error returned from the `validate_rbac_registration` method. -#[allow(clippy::large_enum_variant)] -pub enum RbacValidationError { - /// A registration is invalid (`report.is_problematic()` returns `true`). - /// - /// This variant is inserted to the `rbac_invalid_registration` table. - InvalidRegistration { - /// A Catalyst ID. - catalyst_id: CatalystId, - /// A registration purpose. - purpose: Option, - /// A problem report. - report: ProblemReport, - }, - /// Unable to determine a Catalyst ID of the registration. - /// - /// This can happen if a previous transaction ID in the registration is incorrect. - UnknownCatalystId, - /// A "fatal" error occurred during validation. - /// - /// This means that the validation wasn't performed properly (usually because of a - /// database failure) and we cannot process the given registration. This error is - /// propagated on a higher level, so there will be another attempt to index that - /// block. - Fatal(anyhow::Error), -} - -impl From for RbacValidationError { - fn from(e: anyhow::Error) -> Self { - RbacValidationError::Fatal(e) - } -}