Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FactorInstancesProvider #18

Merged
merged 73 commits into from
Oct 15, 2024
Merged

FactorInstancesProvider #18

merged 73 commits into from
Oct 15, 2024

Conversation

CyonAlexRDX
Copy link
Contributor

@CyonAlexRDX CyonAlexRDX commented Oct 9, 2024

CodeCoverage is higher than reported, many false negatives due to .await.

Important

This implementation does NOT use the index of the last FactorInstance to be taken from the Cache, in order to derive instances AFTER that index. This means that if the FactorInstancesProvider is used without a Profile, then 0' / 0^ (^ is new notation meaning "hardened and in KeySpace::Securified) will be used
see https://github.com/radixdlt/sargon-mfa/blob/factor_instances_provider/src/factor_instances_provider/provider/provider_adopters/virtual_entity_creating_instance_provider.rs#L491
This feels OK, since in most operations we use Profile.
It is important we remember this when we build Account Recovery Scan, because it would start over at 0' when all instances from cache have been used. A simple solution to this would be to allow keeping some local offset counter outside of FactorInstancesProvider and allow injecting it / passing it as argument to the FactorInstancesProvider or FactorInstancesCache, typically to be set on NextDerivationEntityIndexWithEphemeralOffsets.

KeySpace

/// A division of the key space used by CAP26 derivation paths.
enum KeySpace {
    /// Used by creation of Unsecurified Babylon Accounts and Personas exactly as done today. `0'`, `1'` etc, where `'` denotes "hardened"
    Unsecurified,
   /// New, used by MFA FactorInstances, which is `0^ = 0' + 2^30`, `1^ = 1' + 2^30`  etc..
    Securified
}

DerivationPreset

/// Essentially a triple `(CAP26EntityKind, CAP26KeyKind, KeySpace)`, or in fact a tuple `(CAP26EntityKind, KeySpace)` since all four variants below have `CAP26KeyKind::TransactionSigning`.
/// if we would add `ROLA` as a preset that would have `CAP26KeyKind::AuthenticationSigning`.
pub enum DerivationPreset {
    AccountVeci,    
    AccountMfa,
    IdentityVeci,
    IdentityMfa,
}

These are the supported "presets" used by the FactorInstancesProvider. If we ever need to derive more factor instances, we derive for all these presets for ALL references FactorSources. However, we only derive is the cache is not "full" for that factor source for that preset. Definition of "full" is if there are CACHE_FILLING_QUANTITY (30) many FactorInstances.

We use CACHE_FILLING_QUANTITY for ALL FactorSourceKinds for ALL Presets. To keep things simple. Meaning per FactorSource there will be CACHE_FILLING_QUANTITY many FactorInstances in Unsecurified KeySpace and CACHE_FILLING_QUANTITY FactorInstances in Securified KeySpace to be used by both Accounts and Identities respectively, in total 4 * CACHE_FILLING_QUANTITY = 120 FactorInstances. We can tweak this in the future if we want.

A DerivationPreset is essentially a triple (CAP26EntityKind, CAP26KeyKind, KeySpace).

IndexAgnosticPath

From the triple (CAP26EntityKind, CAP26KeyKind, KeySpace) we get from DerivationPreset we can give it a NetworkID to get an IndexAgnosticPath

/// A DerivationPath which is not indexed. On a specific network.
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
pub struct IndexAgnosticPath {
    pub network_id: NetworkID,
    pub entity_kind: CAP26EntityKind,
    pub key_kind: CAP26KeyKind,
    pub key_space: KeySpace,
}

Cache

The cache needs to work independent of entity index, meaning we cannot use DerivationPaths as keys, which is quite self-explanatory if you think about this for a couple of seconds... we wanna go to the cache and say, "give me the 5 next free FactorInstances for this DerivationPreset on this network for this FactorSource". Meaning we should not have to think about which indices those next 5 instances have. So instead the cache uses IndexAgnosticPath as Cache Keys. So we have HashMap<IndexAgnosticPath, IndexSet<HierarchicalDeterministicFactorInstance>>.

But I've created a struct FactorInstances(IndexSet<HierarchicalDeterministicFactorInstance>) new type, so we cache:

HashMap<IndexAgnosticPath, FactorInstances>

But we support multiple FactorSources... so the cache is:

#[derive(Debug, Default, Clone)]
pub struct FactorInstancesCache {
    /// PER FactorSource PER IndexAgnosticPath FactorInstances (matching that IndexAgnosticPath)
    pub values: IndexMap<FactorSourceIDFromHash, IndexMap <IndexAgnosticPath, FactorInstances>>,
}

FactorInstancesProvider evolution

Init

+ pub struct FactorInstancesProvider;

+ impl FactorInstancesProvider {
+     /// Provides `number_of_accounts` many `HDFactorInstances` for every
+     /// FactorSource in `matrix_of_factor_sources`
+     pub fn get(
+         cache: &Cache, // Can be empty/unavailable if so we can `::default()` to empty
+         matrix_of_factor_sources: &MatrixOfFactorSources,
+         number_of_accounts: usize,
+     ) -> IndexMap<FactorSourceID, Index<HDFactorInstance>> {
+         ...
+     }
+ }

Naive

impl FactorInstancesProvider {
    /// Provides `number_of_accounts` many `HDFactorInstances` for every
    /// FactorSource in `matrix_of_factor_sources`
-    pub fn get(
+    pub async fn get(
        cache: &Cache, // Can be empty/unavailable if so we can `::default()` to empty
        matrix_of_factor_sources: &MatrixOfFactorSources,
        number_of_accounts: usize,
-    ) -> IndexMap<FactorSourceID, Index<HDFactorInstance>> {
+    ) -> Result<IndexMap<FactorSourceID, Index<HDFactorInstance>>> {
+       if let Some(loaded) = cache.get(matrix_of_factor_sources, number_of_accounts) {
+           loaded
+       }
+       return Self::derive_more(matrix_of_factor_sources, number_of_accounts).await
    }
}

That would not be so helpful an implementation, since what would happen if the cache
contains less than number_of_accounts of FactorInstances for any of the FactorSources?

So the solution must be more sophisticated.

"Partial" Load from cache / Derive More

impl FactorInstancesProvider {
    /// Provides `number_of_accounts` many `HDFactorInstances` for every
    /// FactorSource in `matrix_of_factor_sources`
    pub async fn get(
        cache: &Cache, // Can be empty/unavailable if so we can `::default()` to empty
        matrix_of_factor_sources: &MatrixOfFactorSources,
        number_of_accounts: usize,
    ) -> Result<IndexMap<FactorSourceID, Index<HDFactorInstance>>> {
-       if let Some(loaded) = cache.get(matrix_of_factor_sources, number_of_accounts) {
-           loaded
-       }
-       return Self::derive_more(matrix_of_factor_sources, number_of_accounts)
+       // "pf" short for "Per FactorSource"
+       let pf_from_cache: IndexMap<FactorSourceID, Index<HDFactorInstance>> = cache
+           .get(matrix_of_factor_sources, number_of_accounts);
+       let pf_quantity_to_derive = pf_from_cache.iter().filter_map(|(factor_source_id, found_in_cache)| {
+           let qty_missing_from_cache = number_of_accounts - found_in_cache.len();
+           if qty_missing_from_cache <= 0 {
+               // no instances missing, cache can fully satisfy request amount
+               None
+           } else {
+               // If we are gonna derive anyway, lets derive so that we can fulfill the `number_of_accounts`
+               // originally request + have `CACHE_FILLING_SIZE` many more left after, a.k.a. full cache!
+               let qty_to_derive = CACHE_FILLING_SIZE + number_of_accounts - found_in_cache.len();
+               Some(qty_to_derive)
+           }
+       }).collect::<IndexMap<FactorSourceID, usize>>();
+
+       let pf_newly_derived = Self::derive_more(pf_quantity_to_derive).await?;
+
+       let pf_merged = matrix_of_factor_sources.all().iter().map(|f|) {
+           let mut merged = IndexSet::new();
+           let from_cache = pf_from_cache.get(f).unwrap_or_default();
+           let newly_derived = pf_newly_derived.get(f).unwrap_or_default();
+           merged.extend(from_cache); // from cache first
+           merged.extend(newly_derived);
+
+           (f, merged)
+       }.collect::<IndexMap<FactorSourceID, Index<HDFactorInstance>>>();
+
+       Ok(pf_merged)
    }
}

Important

We are getting somewhere, but we have omitted fundamental logic, which
is which Derivation Entity Indices to use? Remember our DerivationPath
"m/44'/1022'/<NETWORK>'/<ENTITY_KIND>'/<KEY_KIND>'/<ENTITY_INDEX>'"
What to use for ENTITY_INDEX>'?
The type is EntityIndex and it is a u32 with either + 2^30 if
it is in the securified keyspace or + 0, if in unsecurified keyspace.

+ pub struct NextIndexAssigner {
+   /// Can be empty/unavailable if so we can `::default()` to empty
+   cache: &Cache,
+
+   /// `None` for Account Recovery Scan
+   maybe_profile: Option<Profile>
+ }
+
+ pub struct InstanceQuery {
+    network_id: NetworkID,
+    factor_source_id: FactorSourceID,
+    derivation_preset: DerivationPreset
+}
+
+ impl NextIndexAssigner {
+   fn max_from_profile(
+       &self, query: InstanceQuery
+   ) -> Option<EntityIndex> {
+       ...
+   }
+   fn max_from_cache(
+       &self, query: InstanceQuery
+   ) -> Option<EntityIndex> {
+       ...
+   }
+
+   pub next(
+       &self, query: InstanceQuery
+   ) -> EntityIndex {
+       let max_from_cache = self.max_from_cache(query).unwrap_or(0);
+       let max_from_profile = self.max_from_profile(query).unwrap_or(0);
+       let max = std::cmp::max(max_from_cache, max_from_profile);
+
+       max.add(1) // next
+   }
+ }
impl FactorInstancesProvider {
    /// Provides `number_of_accounts` many `HDFactorInstances` for every
    /// FactorSource in `matrix_of_factor_sources`
    pub async fn get(
-        cache: &Cache, // Can be empty/unavailable if so we can `::default()` to empty
        matrix_of_factor_sources: &MatrixOfFactorSources,
        number_of_accounts: usize,
+       network_id: NetworkID,
+       next_index_assigner: &NextIndexAssigner,
    ) -> Result<IndexMap<FactorSourceID, Index<HDFactorInstance>>> {
+       // `(EntityKind::Account, KeySpace::Securified, KeyKind::TX)`
+       let derivation_preset = DerivationPreset::AccountMfa;
-       let pf_from_cache: IndexMap<FactorSourceID, Index<HDFactorInstance>> = cache
-           .get(matrix_of_factor_sources, number_of_accounts);
+       let pf_from_cache: IndexMap<FactorSourceID, Index<HDFactorInstance>> = next_index_assigner.
+           // previous example was overly simplified
+           // must provide `DerivationPreset` and `NetworkID`!
+           cache.get(matrix_of_factor_sources, number_of_accounts, derivation_preset, network_id);
+  
        ...
-       let pf_newly_derived = Self::derive_more(pf_quantity_to_derive).await?;
+       let pf_newly_derived = Self::derive_more(
+            pf_quantity_to_derive,
+            network_id,
+            derivation_preset
+        ).await?;
        ...
    }

+   async fn derive_more(
+        pf_quantity_to_derive: IndexMap<FactorSourceID, usize>,
+        network_id: NetworkID,
+        derivation_preset: DerivationPreset,
+        next_index_assigner: &NextIndexAssigner
+   ) -> Result<IndexMap<FactorSourceID, Index<HDFactorInstance>>> {
+        let pf_paths = pf_quantity_to_derive.into_iter().map(|(factor_source_id, qty)| {
+           // `qty` many paths 
+           let paths_for_factor = (0..qty).map(|_| {
+               let query = InstanceQuery {
+                   network_id,
+                   factor_source_id,
+                   derivation_preset
+               };
+               let index =  next_index_assigner.next(query);
+               DerivationPath::from((query, index))
+           }).collect::<IndexSet<DerivationPath>>();
+           (f, paths)
+        }).collect::<IndexMap<FactorSourceID, IndexSet<DerivationPath>>>();
+        
+       KeysCollector::new(pf_paths, ...).collect().await
+   }
}

Important

But it is more complex than that, subsequent calls to next_index_assigner.next
will return the same index! So the NextIndexAssigner must keep some "local"
counters, lets call them, "ephemeral offsets" per factor source, so that subsequent
calls to next returns differnet ids!

pub struct NextIndexAssigner {
-   cache: &Cache,
+   next_index_assigner_analyzing_cache: NextIndexAssignerAnalyzingCache

-   maybe_profile: Option<Profile>
+   next_index_assigner_analyzing_profile: NextIndexAssignerAnalyzingProfile,

+   ephemeral_offsets: NextIndexAssignerEphemeralOffsets,
}

+ pub struct NextIndexAssignerAnalyzingProfile {
+    /// `None` for Account Recovery Scan
+   maybe_profile: Option<Profile>
+ }
+ impl NextIndexAssignerAnalyzingProfile {
+   fn max(
+       &self, query: InstanceQuery
+   ) -> Option<EntityIndex> {
+       ...
+   }
+   fn next(
+       &self, query: InstanceQuery
+   ) -> EntityIndex {
+       let next = self.max(query).unwrap_or_default(0);
+       next + 1
+   }
+}

+ pub struct NextIndexAssignerAnalyzingCache {
+   /// Can be empty/unavailable if so we can `::default()` to empty
+   cache: &Cache,
+ }
+ impl NextIndexAssignerAnalyzingCache {
+   fn max(
+       &self, query: InstanceQuery
+   ) -> Option<EntityIndex> {
+       ...
+   }
+   fn next(
+       &self, query: InstanceQuery
+   ) -> EntityIndex {
+       let next = self.max(query).unwrap_or_default(0);
+       next + 1
+   }
+}

+ pub struct NextIndexAssignerEphemeralOffsets {
+       // per factor per network per DerivationPreset, a counter with an offset
+       ephemeral_offsets: HashMap<FactorSourceID, HashMap<NetworkID, HashMap<DerivationPreset, usize>>>
+ }
+ impl NextIndexAssignerEphemeralOffsets {
+   fn reserve(&self, query: InstanceQuery) {
+       ...
+   }
+ }


impl NextIndexAssigner {
-   fn max_from_profile(
-       &self, query: InstanceQuery
-   ) -> Option<EntityIndex> {
-       ...
-   }

-   fn max_from_cache(
-       &self, query: InstanceQuery
-   ) -> Option<EntityIndex> {
-       ...
-   }

+   fn cache(&self) -> &mut Cache {
+       &mut self.next_index_assigner_analyzing_cache.cache()
+   }

    pub next(
        &self, query: InstanceQuery
    ) -> EntityIndex {
        let max_from_cache = self.next_index_assigner_analyzing_cache.next(query);
        let max_from_profile = self.next_index_assigner_analyzing_profile.next(query);
        let max = std::cmp::max(max_from_cache, max_from_profile);
-        max.add(1) // next
+        let ephemeral_offset = self.ephemeral_offsets.reserve(query);
+       max.add(ephemeral_offset)
    }
}

Important

But it is not as simple as that, if we are gonna prompt user to derive more keys
using the KeysCollector, we might as well take the opportunity to fill the cache
for other DerivationPresets as well. In the example above we are providing
FactorInstances for DerivationPreset::AccountMfa but what if the cache does not
contain CACHE_FILLING_SIZE many factors for DerivationPreset::IdentityVeci?
We should derive for that to, and fill the cache! Just like we fill the cache
for DerivationPreset::all() when we are adding a new FactorSource!

implementation FactorInstancesProvider
    async fn derive_more(
         pf_quantity_to_derive: IndexMap<FactorSourceID, usize>,
         network_id: NetworkID,
-         derivation_preset: DerivationPreset,
+        originally_requested_derivation_preset: DerivationPreset,
         next_index_assigner: &NextIndexAssigner
    ) -> Result<IndexMap<FactorSourceID, Index<HDFactorInstance>>> {
         let pf_paths = pf_quantity_to_derive.into_iter().map(|(factor_source_id, qty)| {
            // `qty` many paths                                                ^  ^  
-            let paths_for_factor = (0..qty).map(|_| {                         *  |              
+            let originally_requested_paths_for_factor = (0..qty).map(|_| {    |  *          
                let query = InstanceQuery {                                    *  |      
                    network_id,                                                |  *      
                    factor_source_id,                                          *  |          
                    derivation_preset                                          |  *  
                };                                                             *  |  
                let index =  next_index_assigner.next(query);                  |  *          
                DerivationPath::from((query, index))                           *  |          
            }).collect::<IndexSet<DerivationPath>>();                          |  *      
                                                                               *  |          
+           let cache_filling_paths = DerivationPreset::all()                  |  *          
+               .excluding(originally_requested_derivation_preset)             *  |              
+               .into_iter()                                                   |  *      
+               .flat_map(|derivation_preset| {                                *  |              
+                   let cache = next_index_assigner.cache;                     |  *      
+                                                                              *  |           
+                   let single_factor_from_cache = cache                       |  * 
+                       .get_for_single_factor_source(                         *  |    
+                           factor_source_id, // <-- SINGLE *-*-*-*-*-*-*-*-*-*/ /
+                           qty, // -*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-**-*-*-*/
+                           derivation_preset, 
+                           network_id
+                       );
+                   let qty_to_be_full = CACHE_FILLING_SIZE - single_factor_from_cache.len();
+                   // `qty_to_be_full` is `0` if cache full, and the map below => empty
+                   (0..qty_to_be_full).map(|_| {
+                       let query = InstanceQuery {
+                           network_id,
+                           factor_source_id,
+                           derivation_preset
+                       };
+                       let index =  next_index_assigner.next(query);
+                       DerivationPath::from((query, index))
+                   }).collect::<IndexSet<DerivationPath>>()
+               })
+               .collect::<IndexSet<DerivationPath>>();
+
+           let mut paths = IndexSet::new();
+           paths.extend(originally_requested_paths_for_factor);
+           paths.extend(cache_filling_paths);
            (f, paths)
         }).collect::<IndexMap<FactorSourceID, IndexSet<DerivationPath>>>();
         
        KeysCollector::new(pf_paths, ...).collect().await
    }

Alas...

Important

It is more complex than this, because we must

  1. Delete Factorinstances read from cache used to fulfill request
  2. Split newly derived FactorInstances in two, some to use to fulfill request, rest to cache
impl FactorInstancesProvider {
    /// Provides `number_of_accounts` many `HDFactorInstances` for every
    /// FactorSource in `matrix_of_factor_sources`
    pub async fn get(
        pf_quantity_to_derive: IndexMap<FactorSourceID, usize>,
        network_id: NetworkID,
        originally_requested_derivation_preset: DerivationPreset,
        next_index_assigner: &NextIndexAssigner
    ) -> Result<IndexMap<FactorSourceID, Index<HDFactorInstance>>> {
        // `(EntityKind::Account, KeySpace::Securified, KeyKind::TX)`
        let derivation_preset = DerivationPreset::AccountMfa;
        // "pf" short for "Per FactorSource"
        let pf_from_cache: IndexMap<FactorSourceID, Index<HDFactorInstance>> = next_index_assigner.
            cache.get(matrix_of_factor_sources, number_of_accounts, derivation_preset, network_id);
  
+       let pf_quantity_missing_from_cache = IndexMap<FactorSourceID, usize>::new():
        let pf_quantity_to_derive = pf_from_cache.iter().filter_map(|(factor_source_id, found_in_cache)| {
            let qty_missing_from_cache = number_of_accounts - found_in_cache.len();
            if qty_missing_from_cache <= 0 {
                // no instances missing, cache can fully satisfy request amount
                None
            } else {
+               // We must retain how many were missing from cache so that we can know how many of 
+               // the newly derived we should use directly and how many to cache
+               pf_quantity_missing_from_cache.insert(factor_source_id, qty_missing_from_cache)                
                // If we are gonna derive anyway, lets derive so that we can fulfill the `number_of_accounts`
                // originally request + have `CACHE_FILLING_SIZE` many more left after, a.k.a. full cache!
                let qty_to_derive = CACHE_FILLING_SIZE + number_of_accounts - found_in_cache.len();
                Some(qty_to_derive)
            }
        }).collect::<IndexMap<FactorSourceID, usize>>();

        let pf_newly_derived = Self::derive_more(pf_quantity_to_derive, network_id, derivation_preset).await?;

        let pf_mixed = matrix_of_factor_sources.all().iter().map(|f|) {
            let mut merged = IndexSet::new();
            let from_cache = pf_from_cache.get(f).unwrap_or_default();
            let newly_derived = pf_newly_derived.get(f).unwrap_or_default();
            merged.extend(from_cache); // from cache first
            merged.extend(newly_derived);

            (f, merged)
        }.collect::<IndexMap<FactorSourceID, Index<HDFactorInstance>>>();

+       let mut pf_to_cache = IndexMap::new();
+       let mut pf_to_use_directly = IndexMap::new();
+       for (factor_source_id, factor_instances) in pf_mixed {
+           let instances_by_derivation_preset = factor_instances
+                   .into_iter()
+                   .into_group_map_by(|f| {
+                       DerivationPreset::try_from(f.derivation_path()).expect("Only valid Presets")
+                   })
+                   .into_iter()
+                   .collect::<IndexMap<DerivationPreset, IndexSet<HDFactorInstance>>>>();
+           for (derivation_preset, instances) in instances_by_derivation_preset {
+               if derivation_preset == originally_requested_derivation_preset {
+                   // Must apply split logic
+                   let quantity_missing_from_cache = pf_quantity_missing_from_cache.get(factor_source_id)
+                       .expect("Programmer error, should have saved how many instances remains to fulfill");
+                   let (to_use_directly, to_cache) = instances.split_at(quantity_missing_from_cache);
+                   pf_to_use_directly.append_or_insert(factor_source_id, to_use_directly);
+                   pf_to_cache.append_or_insert(factor_source_id, to_cache);
+               } else {
+                   // Extra derived to fill cache, can simply cache all!
+                   pf_to_cache.append_or_insert(factor_source_id, instances);
+               }
+           }
+       }
+
+       next_index_assigner.cache.delete(pf_from_cache);
+       next_index_assigner.cache.insert(pf_to_cache);
+
+       Ok(pf_to_use_directly)
    }
}

Adoption in Sargon and Future work

The overly simplified SargonOS in this PR contains many not-too-far-from-being-production-ready implementations of usages of the FactorInstancesProvider and the adopter types VirtualEntityCreatingInstanceProvider and SecurifyEntityFactorInstancesProvider which both are essentially wrappers around the FactorInstancesProvider but with the appropriate set of parameters for the two operations: create VECI and securify entity respectively. We should probably not copy paste them over, but we can use them as inspiration, and I expect only very small changes to the parameter and usage.

Derive instances for new FactorSource

https://github.com/radixdlt/sargon-mfa/blob/factor_instances_provider/src/factor_instances_provider/provider/test_sargon_os.rs#L254-L293

New (Unsecurified) Virtual Entity

Create new virtual entities using the FactorInstancesProvider:
https://github.com/radixdlt/sargon-mfa/blob/factor_instances_provider/src/factor_instances_provider/provider/test_sargon_os.rs#L96-L138

Securifying Entities

Securying Entities using the FactorInstancesProvider:
https://github.com/radixdlt/sargon-mfa/blob/factor_instances_provider/src/factor_instances_provider/provider/test_sargon_os.rs#L166-L243

Copy link

codecov bot commented Oct 9, 2024

Codecov Report

Attention: Patch coverage is 90.68323% with 60 lines in your changes missing coverage. Please review.

Project coverage is 95.0%. Comparing base (66189e9) to head (e824680).
Report is 74 commits behind head on main.

Files with missing lines Patch % Lines
...ces_provider/provider/factor_instances_provider.rs 79.8% 27 Missing ⚠️
...pters/securify_entity_factor_instances_provider.rs 64.7% 12 Missing ⚠️
...me/factor_instances_provider_outcome_for_factor.rs 11.1% 8 Missing ⚠️
..._assigner/next_derivation_entity_index_assigner.rs 82.3% 3 Missing ⚠️
...ivation_entity_index_profile_analyzing_assigner.rs 96.0% 3 Missing ⚠️
...tances_provider/provider/factor_instances_cache.rs 97.8% 2 Missing ⚠️
src/types/new_types/securified_persona.rs 92.0% 2 Missing ⚠️
...come/internal_factor_instances_provider_outcome.rs 97.8% 1 Missing ⚠️
src/types/new_types/is_securified_entity.rs 94.7% 1 Missing ⚠️
src/types/new_types/key_space.rs 80.0% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##            main     #18     +/-   ##
=======================================
+ Coverage   94.3%   95.0%   +0.6%     
=======================================
  Files         57      76     +19     
  Lines       1288    1885    +597     
=======================================
+ Hits        1215    1791    +576     
- Misses        73      94     +21     
Files with missing lines Coverage Δ
...ances_provider/agnostic_paths/derivation_preset.rs 100.0% <100.0%> (ø)
...ces_provider/agnostic_paths/index_agnostic_path.rs 100.0% <100.0%> (ø)
...der/agnostic_paths/quantified_derivation_preset.rs 100.0% <100.0%> (ø)
...erivation_entity_index_cache_analyzing_assigner.rs 100.0% <100.0%> (ø)
..._derivation_entity_index_with_ephemeral_offsets.rs 100.0% <100.0%> (ø)
..._index_with_ephemeral_offsets_for_factor_source.rs 100.0% <100.0%> (ø)
...tor_instances_provider/provider/keyed_instances.rs 100.0% <100.0%> (ø)
...vider/outcome/factor_instances_provider_outcome.rs 100.0% <100.0%> (ø)
...al_factor_instances_provider_outcome_for_factor.rs 100.0% <100.0%> (ø)
...pters/virtual_entity_creating_instance_provider.rs 100.0% <100.0%> (ø)
... and 15 more

... and 1 file with indirect coverage changes

@CyonAlexRDX CyonAlexRDX force-pushed the factor_instances_provider branch from f5d61c3 to 8136341 Compare October 11, 2024 09:21
if instances.first().unwrap().derivation_entity_base_index()
!= last.derivation_entity_base_index() + 1
{
warn!(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically want all indices to be contiguous - as in no "gaps" - but in case of legacy Profile that will not be true, so we can typically not enforce it. Perhaps this warn! should be changed to info!....

/// **OR LESS**, never more, and if less, it means we MUST derive more, and if we
/// must derive more, this function returns the quantities to derive for each factor source,
/// for each derivation preset, not only the originally requested one.
pub fn get_poly_factor_with_quantities(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be cool if anyone of you can clean this up, quite a fun challenge :) probably we can use the same code for all DerivationPreset::all() to produce some intermediary result, and then conditionally reduce into CachedInstancesWithQuantitiesOutcome...

CyonAlexRDX and others added 3 commits October 14, 2024 12:00
… of it, and changing the FactorInstancesProvider to have fields and use methods rather than functions.
…ethod

split out special functions on FactorInstancesProvider to be adopters…
…d important missing unit tests asserting that the Cache remains unchanged if the KeysCollector fails deriving keys.
assert_eq!(
os.cache_snapshot(),
cache_before_fail,
"Cache should not have changed when failing."
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important behaviour! This unit tests proves that we only mutate the cache in case of success!

Copy link
Contributor

@matiasbzurovski matiasbzurovski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! 👏

Added some comments/questions, will revisit FactorInstancesProvider again later

Copy link

@GhenadieVP GhenadieVP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good overall, and code quality is more than acceptable :). Amazing job!

pub fn key_kind(&self) -> CAP26KeyKind {
match self {
Self::AccountVeci | Self::IdentityVeci | Self::AccountMfa | Self::IdentityMfa => {
CAP26KeyKind::TransactionSigning

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this possibly be expanded?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes if we would add ROLA keys. And then I want this match to stop compiling :)

Copy link
Contributor

@matiasbzurovski matiasbzurovski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants