-
Notifications
You must be signed in to change notification settings - Fork 83
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
feat: Add new Wallet trait #1071
Conversation
f7dee38
to
e0f2c25
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #1071 +/- ##
========================================
- Coverage 0.05% 0.05% -0.01%
========================================
Files 471 472 +1
Lines 24009 24024 +15
Branches 4306 4293 -13
========================================
Hits 13 13
- Misses 23995 24010 +15
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of observations after a brief skim
#[async_trait] | ||
pub trait DidWallet { | ||
type DidAttrs; | ||
type CreatedDid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this type be different from Did
and how?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a custom type is less restrictive, but using Did
here would be ok:
async fn create_did(&self, attrs: Self::DidAttrs) -> VcxCoreResult<Did>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found out that Did
struct actually comes from libvdrtools, so we would need vdrtools_wallet
compile flag to use the trait if we make it a return type. Keeping for now as is, let me know if we really want to change it. In the meantime, any implementation can do type CreatedDid = Did;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't mean Did
from libvdrtools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which one did you mean?
pub trait DidWallet { | ||
type DidAttrs; | ||
type CreatedDid; | ||
type DidKey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this type be different from PublicKey
and how?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends on how much restriction we want to place on implementations. Same as above, we could just omit this type and use PublicKey
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar as above, PublicKey
comes from libvdrtools. It also seems that ed25519 is the only type supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't mean PublicKey
from libvdrtools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mirgee what did you mean?
type DidKey; | ||
type KeyAttrs; | ||
|
||
async fn create_key(&self, key_attrs: Self::KeyAttrs) -> VcxCoreResult<()>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What will happen if a key with name foo
is created twice? What will happen if a key with name foo
has an associated DID and a key with name foo
is created again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not allowing multiple keys with the same name seems like the most sensible solution to me and Askar has a schema constraint that enforces unique names for keys.
But how to handle key creation is actually on the implementation.
|
||
async fn did_key(&self, did: &str) -> VcxCoreResult<Self::DidKey>; | ||
|
||
async fn replace_did_key(&self, did: &str) -> VcxCoreResult<Self::DidKey>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this supposed to do? If it creates new key, what KeyAttrs
will be used to create it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should set a new key for did, the signature will need a change.
|
||
async fn sign( | ||
&self, | ||
verkey_name: &str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we just pass around the public key - verkey name is a new concept, introducing may be quite consequential...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to just verkey
.
|
||
async fn verify( | ||
&self, | ||
vk: &str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make PublicKey
the argument here? I suppose the signature type can be inferred from the key type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public key only supports ed25519.
pub trait RecordWallet { | ||
type Record; | ||
type RecordId; | ||
type FoundRecord; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure whether FoundRecord
and Record
will end up being different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be rare, but they might be. Askar allows specifying expiry when creating/updating a record, but expiry is not returned when fetching the record.
|
||
async fn get_record(&self, id: &Self::RecordId) -> VcxCoreResult<Self::FoundRecord>; | ||
|
||
async fn update_record(&self, update: Self::Record) -> VcxCoreResult<()>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the ID of the record being updated here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is hidden behind the Self::Record
type - there is no guarantee that different implementations will use the same id type, so we cannot have something like:
async fn update_record(&self, id: &str, update_attrs: Self::Record) -> VcxCoreResult<()>;
async fn search_record( | ||
&self, | ||
filter: Self::SearchFilter, | ||
) -> VcxCoreResult<BoxStream<VcxCoreResult<Self::FoundRecord>>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused about the return type here.
- The implementation's
SearchFilter
containslimit
, option, but this function seems to return a single record? Can't multiple records match the filter and if so, what is the behavior if they do? - What is the reasoning behind using
BoxStream
(and thus hardcodingfutures
into the signature)? - What is the reasoning behind the
Result
"nesting"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This method returns a stream in
Result
, streams can contain multiple items. Multiple records can match the filter, I would expect all matches to be included in result stream. - This method signature was "borrowed" so I think the idea was to have lazily evaluated search.
- When fetching each record lazily, each fetch can fail.
The "eager" variant could simplify the method signature into something like -> VcxCoreResult<Vec<Self::FoundRecord>>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It now simplified.
49f542a
to
542eac0
Compare
Signed-off-by: Ondrej Prazak <[email protected]>
542eac0
to
a80bc06
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is disruptive comment but come to think about it, I am not if it's good idea to use associated types at all after all. I think associated type bring level of generalization we don't need because ultimately we want to end up with Askar implementation, and we definitely want to have stable, singular wallet interface we consume from aries_vcx
.
This is obivous statement, but let me still type it out, because it's important - using associated types means that ultimately the implemented interfaces are different. This will imply the code consuming such traits - DidWallet
, RecordWallet
can not be used interchangeable, without modifying the code using them.
So while we could have 2 implementation of DidWallet
something like:
AskarDidWallet
, (implementing traitDidWallet(--Askar types--)
)VdrtoolsDidWallet
(implementing traitDidWallet(--Vdrtools types--)
Although the the name of the methods are the same, it's not the same interface. Again, I am saying obivous statements but I want to point out the impacts this will have on the upstream consumers (aries_vcx
crate).
BecauseVdrtoolsDidWallet
to useAskarDidWallet
they are not interchangeable (different argument types, different return types). Cratearies_vcx
would need to decide to either work with one or the other. Can't support both at once without either:- feature flags
- additional wrapper layer funneling the differences into single interface
Interchangeability of implementations is great property which makes things a lot easier from organizational perspective, as it doesn't require binary switch of consuming code, and therefore:
- everyone can switch from one impl to another whenever ready rather than being enforced,
- no ultra-long-lived PRs,
- less likelyhood of merge conflicts.
I would like to suggest different strategy in multiple phases and steps.
Phase 1 - interchangeability
- Design a trait, taking askar needs in consideration, albeit not perfect interface, not exposing all of Askar capabilities right off the bat.
- Implement the trait for vdrtools wallet first
- Update all consuming code (such as
aries_vcx
) to use the new trait - Implement the trait for askar
At that point, we have one and only one interface for both askar
and vdrtools
implementations. The two implementations are interchangeable. It's possible to run integration tests with either wallet implementation.
Phase 2 - migration
This is further down the line so it might flesh out differently, but something along lines:
- Provide migration scripts to migrate
vdrtools
base wallet toaskar
implementation - Deprecate/Delete
vdrtools
implementation - Further improve the new trait to expose more of askar capabilities
I am sorry for voicing this concern only now. I think the Bogdan's PR was somewhat overwhelming in different terms (the technicalities) which made me to miss the important implications I am voicing here. The traits with associated types in this simpler form made it easier to see bigger picture.
I would like to also hear @gmulhearn thoughts on the matter, as you've already have some experience with askar too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I agree with this comment. I'm having a hard time imagining how these associated types would be used by code within the aries_vcx crate. Or perhaps how it would be used by other aries_vcx_core components (such as by Anoncreds trait, which has functions that take in a Wallet impl).
For instance, when anoncreds stores a credential, it'll need to translate a Credential structure into a Wallet::Record. So I suppose Anoncreds trait would maybe need to specify that Wallet::Record satisfies From (or something like that). We might end up loading up traits with where
constraints etc. I think this trail of trait constraints is how Bogdan's POC PR got to where it got to. Which is maybe fine depending on what Rust dev you ask.
Maybe I'm just unsure if we need it right now. The types in vdrtools wallet vs askar are fairly similar, they are both based on the same Aries RFC: https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0050-wallets/README.md . An Aries "record" type is mostly already defined IMO. And the search filter is WQL (as specified in that spec).
IMO The biggest problem with the current trait is that we are using JSON strings everywhere 🤮, so this is a great step towards having good types. Anyway, happy to discuss more. Cheers
Closing in favour of #1084 |
A new trait for wallet heavily inspired by #1042. Associated types are leveraged to provide flexibility to various implementations.