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

Hashmap uniformization #232

Merged
merged 7 commits into from
Aug 25, 2023
Merged

Hashmap uniformization #232

merged 7 commits into from
Aug 25, 2023

Conversation

bobozaur
Copy link
Contributor

@bobozaur bobozaur commented Aug 15, 2023

This PR is somewhat of a continuation to the discussion in #228 .

It allows both FFI and native code to use the services API in an organic manner through the use of generics, particularly referring to instances where there are containers of references in FFI, but in native code the container would be over owned types.

Also, the first commit simplifies the TailsFileReader; some lints were getting triggered and I figured I might as well do that while I was at it. If it's a problem to have both of these things in the same PR I can create a separate one for the first commit and cherry-pick the others in a separate PR.

@bobozaur
Copy link
Contributor Author

@andrewwhitehead @swcurran The ReferencesMap trait uses GATs to achieve the flexibility of passing in maps over owned/referenced values. I took the liberty of also bumping the Rust toolchain version from 1.64 to 1.65, which is when GATs were stabilized, as otherwise CI would fail.

None,
None,
None,
None::<&HashMap<RevocationRegistryDefinitionId, RevocationRegistryDefinition>>,
Copy link
Member

Choose a reason for hiding this comment

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

This awkwardness makes me think we should just use an enum such as:

pub enum CowMap<'a, K, V> {
    Empty,
    Ref<HashMap<&'a K, &'a V>>,
    Owned<HashMap<K, V>>,
}

Maybe?

Copy link
Member

Choose a reason for hiding this comment

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

Empty also isn't really needed, empty()/default() could return Self::Owned(HashMap<K,V>::new()) which doesn't even allocate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I doubt that'll solve anything though. Even if you accept a CowMap<'a, K, V>, the problem is that when you pass None, there's no K or V specified. Which is why the type of the None, or Option must be explicitly set.

But I agree that it's awkward. I'll think about how to approach it better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I gave this quite some thought and I don't really have a great idea on how to go about it.

Once you enter generic land you have to specify the types used, whether it's for a trait or a wrapper type.

Another approach would be to divide the services functions into ones that accept maps of references, which could even just be local to the FFI, and the other ones meant to be publicly used, which would accept maps of owned types and could reside in the services module. It wouldn't even matter if a wrapper type or trait is used then.

@andrewwhitehead what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

If the argument was a concrete type like Option<CowMap<'a, SchemaId, Schema>> then the type for None could be inferred, but you would have to convert hash maps on the way in with something like Some(map.into()).

That said it's starting to look like the best option might be to just clone the objects in the FFI layer, and only accept owned maps in these methods?

Copy link
Contributor Author

@bobozaur bobozaur Aug 16, 2023

Choose a reason for hiding this comment

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

Having the argument as Option<CowMap<'a, SchemaId, Schema>> seems like leaking an implementation detail to consumers, if you ask me. I wouldn't really like having to do that conversion as a user of the library.

Cloning in the FFI layer would be an option, but I still think that specialized functions might still be nicer overall. Something like:

pub fn verify_presentation_ffi<'a, I>(
    presentation: &Presentation,
    pres_req: &PresentationRequest,
    schemas: &HashMap<&SchemaId, &Schema>,
    cred_defs: &HashMap<&CredentialDefinitionId, &CredentialDefinition>,
    rev_reg_defs: Option<&HashMap<&RevocationRegistryDefinitionId, &RevocationRegistryDefinition>>,
    rev_status_lists: Option<I>,
    nonrevoke_interval_override: Option<
        &HashMap<&RevocationRegistryDefinitionId, HashMap<u64, u64>>,
    >,
) -> Result<bool>
where
    I: IntoIterator<Item = &'a RevocationStatusList> + Clone + std::fmt::Debug,
{
    _verify_presentation(
        presentation,
        pres_req,
        schemas,
        cred_defs,
        rev_reg_defs,
        rev_status_lists,
        nonrevoke_interval_override,
    )
}

pub fn verify_presentation<'a, I>(
    presentation: &Presentation,
    pres_req: &PresentationRequest,
    schemas: &HashMap<SchemaId, Schema>,
    cred_defs: &HashMap<CredentialDefinitionId, CredentialDefinition>,
    rev_reg_defs: Option<&HashMap<RevocationRegistryDefinitionId, RevocationRegistryDefinition>>,
    rev_status_lists: Option<I>,
    nonrevoke_interval_override: Option<
        &HashMap<RevocationRegistryDefinitionId, HashMap<u64, u64>>,
    >,
) -> Result<bool>
where
    I: IntoIterator<Item = &'a RevocationStatusList> + Clone + std::fmt::Debug,
{
    _verify_presentation(
        presentation,
        pres_req,
        schemas,
        cred_defs,
        rev_reg_defs,
        rev_status_lists,
        nonrevoke_interval_override,
    )
}

/// Verify an incoming proof presentation
fn _verify_presentation<'a, T, U, V, I, Z>(
    presentation: &Presentation,
    pres_req: &PresentationRequest,
    schemas: &T,
    cred_defs: &U,
    rev_reg_defs: Option<&V>,
    rev_status_lists: Option<I>,
    nonrevoke_interval_override: Option<&Z>,
) -> Result<bool>
where
    T: ReferencesMap<SchemaId, Schema> + std::fmt::Debug,
    U: ReferencesMap<CredentialDefinitionId, CredentialDefinition> + std::fmt::Debug,
    V: ReferencesMap<RevocationRegistryDefinitionId, RevocationRegistryDefinition>
        + std::fmt::Debug,
    I: IntoIterator<Item = &'a RevocationStatusList> + Clone + std::fmt::Debug,
    Z: ReferencesMap<RevocationRegistryDefinitionId, HashMap<u64, u64>> + std::fmt::Debug,
{
    todo!()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would allow type inference to work its magic while not making performance sacrifices like cloning in the FFI layer. And the _ffi functions could really just reside within the FFI layer as that's what they're meant for, so they don't need to be part of the public API.

Copy link
Member

Choose a reason for hiding this comment

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

Seems like a reasonable solution. I think in that case _verify_presentation could just be pub(crate), since verify_presentation_ffi would only be called from one place. That said cloning would simplify the code a lot, and I don't think the performance impact would be significant. If the allocator is that slow, it can be replaced, and none of the cloned objects are likely very large.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright then. I didn't want to suggest cloning because it really only impacts the FFI, not native consumers (which is mainly the reason for this). But I agree that is by far the simplest solution, so I'll go ahead with that since you seem okay with it.

src/error.rs Outdated Show resolved Hide resolved
@andrewwhitehead andrewwhitehead merged commit 58880b3 into hyperledger:main Aug 25, 2023
25 checks passed
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.

2 participants