-
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
[Task] Improve Account API by internalizing commands #325
Comments
Added 'Add the ability to create an identity with an existing keypair to account' to the task as it is a minor account API change. |
@JelleMillenaar what is the intended benefit of reusing an existing keypair here? is this just for convenience or space-saving? Stronghold doesn't support copying keys (maybe this could be added) and sharing keys across multiple identities would require a fair amount of internal changes. If you wanted to create a "child" identity owned by a "parent" identity wouldn't the |
In regards to the simplification & optimization of identity updates, I think we have (at least) these three variants to consider. Aside: I originally thought that we might want to reuse the Variant 1 (current approach) let command = Command::create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.finish()?;
// Process the command and update the identity state on the tangle.
account.update_identity(document, command).await?; Variant 2let command = Command::create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.finish()?;
let other_command = /* omitted */
// Stores the created command in an internal queue in the account.
account.queue_command(command);
// We can queue multiple commands before applying them.
account.queue_command(other_command);
// Applies all pending commands to the document and updates the tangle with the latest document.
account.update_identity(document).await?; Variant 3 account.new_command(document)
.create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.apply() // <-- Updates the tangle immediately.
.await?;
account.new_command(document)
.create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.queue(); // <-- Queues the command for later.
account.apply_pending_commands(document).await?; Variant 2 updates the tangle exactly once, even with multiple commands present. It's possible to build up a queue of commands in the account, and then call identity.rs/identity-account/src/events/command.rs Lines 28 to 59 in 5a35860
This variant's API is a bit less intuitive and more verbose to use than the others. First-time users might not realize they have to call An actual "one-line" update would look something like variant 3. Either apply the command immediately with The current variant 1 is somewhere in between, requiring two method invocations to update an identity, but offers no way to lazily apply multiple commands. Any thoughts on this are very welcome - perhaps I've missed a variant that combines the strengths of some of these. Other than that, what are your preferences? |
Variant 3 seems feasible.
Small correction: the current Implementing a queue can be considered separately as it seems possible with all three of the outlined variants. If I understand correctly, a queue would would need to store both an Nit: I prefer the verb |
@PhilippGackstatter: In Variant 2 we don't need define for which document we want to queue commands, but for applying we do? I see the elegance of Variant 3, but it has same problem of potentially forgetting to actually commiting the updates. |
@eike-hass Part of the issue is adding the ability to queue commands (to combine multiple updates and reduce network chattiness), which is essentially the only thing Variant 2 introduces. The other part is "internalising" commands in the
That could be addressed with a similar "apply-on-drop" system which is currently used in the Another comment on Variant 2 is that the
|
Good points!
Not sure how that would be possible with variant 1, unless we add a second method to
Agreed 🙂
I think with Craig's suggestion, this point no longer stands. It will be stored in the
I'm a bit hesitant to add this, unless there is a strong use case where we have many commands to apply at once. In Rust especially, I would rather do a
For variant 2, I think that makes sense 👍
Why not? Can we not create an identity, then add a method and a service, and only then publish all of it as one document?
I think that's the biggest remaining issue with variant 2 and 3. I'm not a fan of the // A single update in one line
account.new_command(did)
.create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.apply() // <-- Updates the tangle immediately.
.await?;
let command = account.new_command(did).create_method().build();
let other_command = account.new_command(did).create_service().build();
account.apply_batch_updates(&[command, other_command]).await?; in which case we have both the one line update, and the batch update without possibly forgetting it. |
You're right.
Because I think the only way to get an
We can call this above approach "Variant 4" for future reference. It neither fully internalizes the commands nor maintains an internal queue in the What if we have some sort of compound command that can be chained instead of queued? E.g. account.new_command(did)
.create_method()
.chain() // <-- Creates some sort of "compound" command struct/queue to hold multiple commands
.new_command(did)
.create_service()
.chain()
.apply() // <-- Executes all commands in the chain
.await?; I'm a bit iffy on whether the above syntax is possible, so it will probably have to be something more like: account.new_command(did)
.create_method()
.chain(||
account.new_command(did)
.create_service()
.build()
).apply() // <-- Executes all commands in the chain
.await?; Just exploring other possible styles that avoid an internal queue in the |
We can btw. also rename the
It looks like to call
The first of your proposals might be possible. I would only implement this if that is possible - the second seems like too much API complexity given the original goal. I imagine it would also be a challenge to port this to Wasm. The first proposal still seems like a mouthful, too, but fulfills the "batch update in one statement" goal. I think I like the array variant a bit better. For singular updates it has a one-statement solution, but for multiple updates, I find it acceptable to have multiple statements. It then also keeps the Curious to hear what others think, though. |
I feel the |
With the queue out of the way (implemented in #377), I'd like to revive the discussion with a focus on the API, so we can hopefully settle on a design. account
.update_identity(did)
.create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.apply()
.await?; This would be somewhat verbose, but clear in what it does. "Update the identity account
.create_method(did)
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.apply()
.await?; Does this confuse first-time users? We're not creating a method on the account, yet that's one way to read it. The former is perhaps clearer here: we create an intermediary object (the return value of Does either of these seem like a good choice, or can anyone come up with something concise and clear? I also still like the current approach for its clarity, which only gets verbose/repetitive if multiple updates are applied subsequently, due to having to call let command: Command = Command::create_method()
.scope(MethodScope::Authentication)
.fragment("my-auth-key")
.finish()?;
account.update_identity(document, command).await?; |
Prior to this discussion even, we could see if we still want to keep the builder pattern for the commands. To give the full picture, take CreateMethod {
scope: MethodScope,
type_: MethodType,
fragment: String,
} where Command::create_method()
.scope(MethodScope::Authentication)
.finish()?; would throw a runtime error, because the fragment isn't specified. However, this could be prevented at compile-time, by making the required parameters ( This plays into the internalization debate, since option 2 would cobble together the did with the required parameters, which wouldn't be great. In that case, option 1 would be cleaner: account
.update_identity(did)
.create_method("my-auth-key")
.scope(MethodScope::Authentication)
.apply()
.await?; We can also supply the did in account
.create_method("my-auth-key")
.scope(MethodScope::Authentication)
.apply(did)
.await?; |
I would prefer the verbose approach of starting the chain with |
Yes, all of these commands would have a corresponding method. identity.rs/identity-account/src/events/command.rs Lines 32 to 64 in bb6bfa4
|
Then I am strongly in favor of |
Per the discussion in standup today, we have reached consensus on:
|
Description
Small refactor on the account API in order to make updating DID Documents from a two step into a one step process. Currently the Account API allows developers to generate update commands that they have to manage and pass on into the
update_identity
function. These commands can be stored inside the account and can be either published immediately or queued to be published in a single call ofupdate_identity
. If more commands are queued they are published in the order they are added. Later, we can add additional optimization code to combine multiple commands into a single DID update.Motivation
Reduce the complexity of the account even further.
Resources
Example
To-do list
Create a task-specific to-do list . Please link PRs that match the To-do list item behind the item after it has been submitted.
update_identity
function to no longer take a command parameter.document
in account examples toDID
Account
#377.KeyPair
in theAccount
#352Change checklist
Add an
x
to the boxes that are relevant to your changes, and delete any items that are not.and across all bindings whereas possible.The text was updated successfully, but these errors were encountered: