-
Notifications
You must be signed in to change notification settings - Fork 55
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
Support adding/removing services and keys to an ion DID #562
Conversation
Codecov Report
@@ Coverage Diff @@
## main #562 +/- ##
==========================================
- Coverage 26.64% 25.44% -1.21%
==========================================
Files 55 55
Lines 5855 6198 +343
==========================================
+ Hits 1560 1577 +17
- Misses 4023 4346 +323
- Partials 272 275 +3
|
|
||
type UpdateDIDByMethodRequest struct { | ||
// Expected to be populated when `method == "ion"`. Describes the changes that are requested. | ||
StateChange StateChange `json:"stateChange" validate:"required"` |
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 works until we support update for other methods, then we'll probably want to reconsider this API
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 agree. Since we don't have other examples, I think this makes sense for now.
framework.LoggingRespondErrMsg(c, errMsg, http.StatusBadRequest) | ||
return | ||
} | ||
if *method != didsdk.IONMethod.String() { |
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 think this logic is better suited for the service layer, though it is OK to duplicate 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.
The service layer method is called UpdateIONDID
, to which you pass in an UpdateIONDIDRequest
object. By design, it's not possible to call that method without an ION did.
The external facing API is open for evolution. The internal is specific so it's more clear and it's easier to maintain.
pkg/service/did/service.go
Outdated
@@ -167,6 +167,18 @@ func (s *Service) CreateDIDByMethod(ctx context.Context, request CreateDIDReques | |||
return handler.CreateDID(ctx, request) | |||
} | |||
|
|||
func (s *Service) UpdateIONDID(ctx context.Context, request UpdateDIDRequest) (*UpdateDIDResponse, error) { |
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 could work better (but also maybe premature optimization) to have this be a generic UpdateDID
method, and then check if the method is ION. This helps us maintain a consistent abstraction between the router (just focused on http) and service (focused on all DID methods 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.
I do think it's premature. I propose we wait for when we have at least 2 methods for which we'll support updates.
Currently, it's only did:ion and I don't foresee others in the pipeline either.
pkg/service/did/storage.go
Outdated
@@ -69,7 +69,7 @@ func NewDIDStorage(db storage.ServiceStorage) (*Storage, error) { | |||
return &Storage{db: db}, nil | |||
} | |||
|
|||
func (ds *Storage) StoreDID(ctx context.Context, did StoredDID) error { | |||
func StoreDID(ctx context.Context, did StoredDID, tx storage.Tx) error { |
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.
nit:
func StoreDID(ctx context.Context, did StoredDID, tx storage.Tx) error { | |
func StoreDID(ctx context.Context, tx storage.Tx, did StoredDID) error { |
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.
Done
didION := ion.ION(id) | ||
if !didION.IsValid() { | ||
return nil, errors.Errorf("invalid ion did %s", id) | ||
} |
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.
could check to see whether any of the updates are set to make sure it's not a no-op
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 added this validation to ionHandler.UpdateDID
. Could validate in both places, but the ionHandler
one seems more appropriate in case other endpoints end up calling that method.
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.
sounds good
pkg/service/did/ion.go
Outdated
}, | ||
} | ||
|
||
if state.status == "" { |
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.
could this be inverted to reduce 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.
or consider using a switch and 3 helper methods for readability
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 don't think it can be inverted. This works like a state machine. When all updates succeed, status will reflect all 3 states within the same call.
When there are failures in the middle, the last state is kept. This is done to ensure that our version of a DID is kept in sync with what was anchored.
pkg/service/did/ion.go
Outdated
return nil, errors.Wrap(err, "creating update request") | ||
} | ||
|
||
state.preAnchor = &preAnchor{ |
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 be blocked until 539 is in, no?
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.
Depends on how much pushback there will be for #539 :)
|
||
updateKey := updatePrivateKey.ToPublicKeyJWK() | ||
|
||
signer, err := ion.NewBTCSignerVerifier(*updatePrivateKey) |
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 call https://github.com/TBD54566975/ssi-sdk/blob/main/did/ion/operations.go#L175 instead
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 wasn't able to find a way in which I could create an instance of ion.DID
with a custom update key. The only way to create an instance of that struct is by calling NewIONDID, which creates new update keys.
I agree that it would be desirable, but didn't want to block this PR on an SDK update.
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.
ok fair
|
||
type UpdateDIDByMethodRequest struct { | ||
// Expected to be populated when `method == "ion"`. Describes the changes that are requested. | ||
StateChange StateChange `json:"stateChange" validate:"required"` |
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 agree. Since we don't have other examples, I think this makes sense for now.
framework.LoggingRespondErrMsg(c, errMsg, http.StatusBadRequest) | ||
return | ||
} | ||
if *method != didsdk.IONMethod.String() { |
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.
The service layer method is called UpdateIONDID
, to which you pass in an UpdateIONDIDRequest
object. By design, it's not possible to call that method without an ION did.
The external facing API is open for evolution. The internal is specific so it's more clear and it's easier to maintain.
didION := ion.ION(id) | ||
if !didION.IsValid() { | ||
return nil, errors.Errorf("invalid ion did %s", id) | ||
} |
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 added this validation to ionHandler.UpdateDID
. Could validate in both places, but the ionHandler
one seems more appropriate in case other endpoints end up calling that method.
pkg/service/did/ion.go
Outdated
}, | ||
} | ||
|
||
if state.status == "" { |
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 don't think it can be inverted. This works like a state machine. When all updates succeed, status will reflect all 3 states within the same call.
When there are failures in the middle, the last state is kept. This is done to ensure that our version of a DID is kept in sync with what was anchored.
|
||
updateKey := updatePrivateKey.ToPublicKeyJWK() | ||
|
||
signer, err := ion.NewBTCSignerVerifier(*updatePrivateKey) |
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 wasn't able to find a way in which I could create an instance of ion.DID
with a custom update key. The only way to create an instance of that struct is by calling NewIONDID, which creates new update keys.
I agree that it would be desirable, but didn't want to block this PR on an SDK update.
pkg/service/did/ion.go
Outdated
return nil, errors.Wrap(err, "creating update request") | ||
} | ||
|
||
state.preAnchor = &preAnchor{ |
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.
Depends on how much pushback there will be for #539 :)
pkg/service/did/service.go
Outdated
@@ -167,6 +167,18 @@ func (s *Service) CreateDIDByMethod(ctx context.Context, request CreateDIDReques | |||
return handler.CreateDID(ctx, request) | |||
} | |||
|
|||
func (s *Service) UpdateIONDID(ctx context.Context, request UpdateDIDRequest) (*UpdateDIDResponse, error) { |
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 do think it's premature. I propose we wait for when we have at least 2 methods for which we'll support updates.
Currently, it's only did:ion and I don't foresee others in the pipeline either.
pkg/service/did/storage.go
Outdated
@@ -69,7 +69,7 @@ func NewDIDStorage(db storage.ServiceStorage) (*Storage, error) { | |||
return &Storage{db: db}, nil | |||
} | |||
|
|||
func (ds *Storage) StoreDID(ctx context.Context, did StoredDID) error { | |||
func StoreDID(ctx context.Context, did StoredDID, tx storage.Tx) error { |
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.
Done
pkg/service/did/ion.go
Outdated
} | ||
|
||
func isSignatureError(_ error) bool { | ||
// TODO: figure out how to determine this error from the body of the response. |
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.
@decentralgabe could you help pointing me to the type of error that I would get if I send an operation to an ION node which has already been applied?
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'm not sure to be honest, maybe @thehenrytsai would know
pkg/server/router/did.go
Outdated
} | ||
updateIONDIDResponse, err := dr.service.UpdateIONDID(c, *updateDIDRequest) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("could not create DID for method<%s>", *method) |
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.
is this the right error message?
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.
Fixed
if we remove pub keys / add other pub keys, and the private keys are not in the system, this will be like a "gimp" did if I'm understanding this correctly It wont be able to do things like issue a vc and stuff, I think we already have good error messages that would explain that if you try to do something like that. |
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.
@andresuribe87 can we push this through?
2dca27b
to
38c99f4
Compare
Overview
Support adding/removing services and keys to an ion DID. Must be custodied.
Description
This is the last part on #340. Most of the complexity comes from needing to ensure concurrent updates work.
Note that this PR uses the same abstractions from the SDK.
How Has This Been Tested?
Wrote a unit test.