11//! Chain of Cardano registration data
22
33mod update_rbac;
4- pub mod validation;
54
65use std:: {
76 collections:: { HashMap , HashSet } ,
@@ -29,7 +28,6 @@ use crate::{
2928 RoleDataRecord , ValidationSignature ,
3029 } ,
3130 providers:: RbacRegistrationProvider ,
32- registration:: cardano:: validation:: RbacValidationError ,
3331} ;
3432
3533/// Registration chains.
@@ -103,7 +101,7 @@ impl RegistrationChain {
103101 & self ,
104102 report : & ProblemReport ,
105103 provider : & Provider ,
106- ) -> Result < HashSet < VerifyingKey > , RbacValidationError >
104+ ) -> Result < ( ) , RbacValidationError >
107105 where
108106 Provider : RbacRegistrationProvider ,
109107 {
@@ -126,7 +124,7 @@ impl RegistrationChain {
126124 }
127125 }
128126
129- Ok ( keys )
127+ Ok ( ( ) )
130128 }
131129
132130 /// Returns a Catalyst ID.
@@ -310,6 +308,39 @@ impl From<RegistrationChainInner> for RegistrationChain {
310308 }
311309}
312310
311+ /// An error returned from the `validate_rbac_registration` method.
312+ #[ allow( clippy:: large_enum_variant) ]
313+ pub enum RbacValidationError {
314+ /// A registration is invalid (`report.is_problematic()` returns `true`).
315+ ///
316+ /// This variant is inserted to the `rbac_invalid_registration` table.
317+ InvalidRegistration {
318+ /// A Catalyst ID.
319+ catalyst_id : CatalystId ,
320+ /// A registration purpose.
321+ purpose : Option < UuidV4 > ,
322+ /// A problem report.
323+ report : ProblemReport ,
324+ } ,
325+ /// Unable to determine a Catalyst ID of the registration.
326+ ///
327+ /// This can happen if a previous transaction ID in the registration is incorrect.
328+ UnknownCatalystId ,
329+ /// A "fatal" error occurred during validation.
330+ ///
331+ /// This means that the validation wasn't performed properly (usually because of a
332+ /// database failure) and we cannot process the given registration. This error is
333+ /// propagated on a higher level, so there will be another attempt to index that
334+ /// block.
335+ Fatal ( anyhow:: Error ) ,
336+ }
337+
338+ impl From < anyhow:: Error > for RbacValidationError {
339+ fn from ( e : anyhow:: Error ) -> Self {
340+ RbacValidationError :: Fatal ( e)
341+ }
342+ }
343+
313344/// Inner structure of registration chain.
314345#[ derive( Debug , Clone ) ]
315346struct RegistrationChainInner {
@@ -557,6 +588,7 @@ impl RegistrationChainInner {
557588 /// for `previous_txn`.
558589 /// - Returns [`RbacValidationError::InvalidRegistration`] if address/key duplication
559590 /// or validation inconsistencies are detected.
591+ #[ must_use]
560592 pub async fn update_from_previous_txn < Provider > (
561593 & self ,
562594 reg : Cip509 ,
@@ -596,9 +628,6 @@ impl RegistrationChainInner {
596628 }
597629 }
598630
599- // Store values before consuming the registration.
600- let stake_addresses = reg. stake_addresses ( ) ;
601-
602631 // Try to add a new registration to the chain.
603632 let new_chain = chain. update ( reg. clone ( ) ) . ok_or_else ( || {
604633 RbacValidationError :: InvalidRegistration {
@@ -609,7 +638,7 @@ impl RegistrationChainInner {
609638 } ) ?;
610639
611640 // Check that new public keys aren't used by other chains.
612- let public_keys = new_chain. validate_public_keys ( & report, provider) . await ?;
641+ new_chain. validate_public_keys ( & report, provider) . await ?;
613642
614643 // Return an error if any issues were recorded in the report.
615644 if report. is_problematic ( ) {
@@ -622,6 +651,104 @@ impl RegistrationChainInner {
622651
623652 Ok ( new_chain)
624653 }
654+
655+ /// Attempts to initialize a new RBAC registration chain
656+ /// from a given CIP-509 registration, ensuring uniqueness of Catalyst ID, stake
657+ /// addresses, and associated public keys.
658+ ///
659+ /// # Returns
660+ /// - `Ok((new_chain, validation_result))` if the chain was successfully initialized
661+ /// and validated.
662+ ///
663+ /// # Errors
664+ /// - [`RbacValidationError::UnknownCatalystId`]: if `reg` lacks a valid Catalyst ID.
665+ /// - [`RbacValidationError::InvalidRegistration`]: if any functional validation,
666+ /// stake address conflict, or public key duplication occurs.
667+ #[ must_use]
668+ pub async fn start_from_provider < Provider > (
669+ & self ,
670+ reg : Cip509 ,
671+ provider : & Provider ,
672+ ) -> Result < RegistrationChain , RbacValidationError >
673+ where
674+ Provider : RbacRegistrationProvider ,
675+ {
676+ let catalyst_id = reg. catalyst_id ( ) . map ( CatalystId :: as_short_id) ;
677+ let purpose = reg. purpose ( ) ;
678+ let report = reg. report ( ) . to_owned ( ) ;
679+
680+ // Try to start a new chain.
681+ let new_chain = RegistrationChain :: new ( reg. clone ( ) ) . ok_or_else ( || {
682+ if let Some ( catalyst_id) = catalyst_id {
683+ RbacValidationError :: InvalidRegistration {
684+ catalyst_id,
685+ purpose,
686+ report : report. clone ( ) ,
687+ }
688+ } else {
689+ RbacValidationError :: UnknownCatalystId
690+ }
691+ } ) ?;
692+
693+ // Verify that a Catalyst ID of this chain is unique.
694+ let catalyst_id = new_chain. catalyst_id ( ) . as_short_id ( ) ;
695+ if provider. is_chain_known ( catalyst_id. clone ( ) ) . await ? {
696+ report. functional_validation (
697+ & format ! ( "{catalyst_id} is already used" ) ,
698+ "It isn't allowed to use same Catalyst ID (certificate subject public key) in multiple registration chains" ,
699+ ) ;
700+ return Err ( RbacValidationError :: InvalidRegistration {
701+ catalyst_id,
702+ purpose,
703+ report,
704+ } ) ;
705+ }
706+
707+ // Validate stake addresses.
708+ let new_addresses = new_chain. stake_addresses ( ) ;
709+ let mut updated_chains: HashMap < _ , HashSet < StakeAddress > > = HashMap :: new ( ) ;
710+ for address in & new_addresses {
711+ if let Some ( id) = provider. catalyst_id_from_stake_address ( address) . await ? {
712+ // If an address is used in existing chain then a new chain must have different role
713+ // 0 signing key.
714+ let previous_chain = provider. chain ( id. clone ( ) )
715+ . await ?
716+ . context ( "{id} is present in 'catalyst_id_for_stake_address', but not in 'rbac_registration'" ) ?;
717+ if previous_chain. get_latest_signing_pk_for_role ( & RoleId :: Role0 )
718+ == new_chain. get_latest_signing_pk_for_role ( & RoleId :: Role0 )
719+ {
720+ report. functional_validation (
721+ & format ! ( "A new registration ({catalyst_id}) uses the same public key as the previous one ({})" ,
722+ previous_chain. catalyst_id( ) . as_short_id( )
723+ ) ,
724+ "It is only allowed to override the existing chain by using different public key" ,
725+ ) ;
726+ } else {
727+ // The new root registration "takes" an address(es) from the existing chain, so
728+ // that chain needs to be updated.
729+ updated_chains
730+ . entry ( id)
731+ . and_modify ( |e| {
732+ e. insert ( address. clone ( ) ) ;
733+ } )
734+ . or_insert ( [ address. clone ( ) ] . into_iter ( ) . collect ( ) ) ;
735+ }
736+ }
737+ }
738+
739+ // Check that new public keys aren't used by other chains.
740+ new_chain. validate_public_keys ( & report, provider) . await ?;
741+
742+ if report. is_problematic ( ) {
743+ return Err ( RbacValidationError :: InvalidRegistration {
744+ catalyst_id,
745+ purpose,
746+ report,
747+ } ) ;
748+ }
749+
750+ Ok ( new_chain)
751+ }
625752}
626753
627754/// Perform a check on the validation signature.
0 commit comments