-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add update assistant for storage migrations (#690)
Signed-off-by: Timo Glastra <[email protected]>
- Loading branch information
1 parent
e4504a4
commit c9bff93
Showing
44 changed files
with
6,523 additions
and
2,033 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# Migrating from AFJ 0.1.0 to 0.2.x | ||
|
||
## Breaking Code Changes | ||
|
||
> TODO | ||
## Breaking Storage Changes | ||
|
||
The 0.2.0 release is heavy on breaking changes to the storage format. This is not what we intend to do with every release. But as there's not that many people yet using the framework in production, and there were a lot of changes needed to keep the API straightforward, we decided to bundle a lot of breaking changes in this one release. | ||
|
||
Below all breaking storage changes are explained in as much detail as possible. The update assistant provides all tools to migrate without a hassle, but it is important to know what has changed. | ||
|
||
See [Updating](./updating.md) for a guide on how to use the update assistant. | ||
|
||
The following config can be provided to the update assistant to migrate from 0.1.0 to 0.2.0: | ||
|
||
```json | ||
{ | ||
"v0_1ToV0_2": { | ||
"mediationRoleUpdateStrategy": "<mediationRoleUpdateStrategy>" | ||
} | ||
} | ||
``` | ||
|
||
### Credential Metadata | ||
|
||
The credential record had a custom `metadata` property in pre-0.1.0 storage that contained the `requestMetadata`, `schemaId` and `credentialDefinition` properties. Later a generic metadata API was added that only allows objects to be stored. Therefore the properties were moved into a different structure. | ||
|
||
The following pre-0.1.0 structure: | ||
|
||
```json | ||
{ | ||
"requestMetadata": <value of requestMetadata>, | ||
"schemaId": "<value of schemaId>", | ||
"credentialDefinitionId": "<value of credential definition id>" | ||
} | ||
``` | ||
|
||
Will be transformed into the following 0.2.0 structure: | ||
|
||
```json | ||
{ | ||
"_internal/indyRequest": <value of requestMetadata>, | ||
"_internal/indyCredential": { | ||
"schemaId": "<value of schemaId>", | ||
"credentialDefinitionId": "<value of credential definition id>" | ||
} | ||
} | ||
``` | ||
|
||
Accessing the `credentialDefinitionId` and `schemaId` properties will now be done by retrieving the `CredentialMetadataKeys.IndyCredential` metadata key. | ||
|
||
```ts | ||
const indyCredential = credentialRecord.metadata.get(CredentialMetadataKeys.IndyCredential) | ||
|
||
// both properties are optional | ||
indyCredential?.credentialDefinitionId | ||
indyCredential?.schemaId | ||
``` | ||
|
||
### Mediation Record Role | ||
|
||
The role in the mediation record was always being set to `MediationRole.Mediator` for both mediators and recipients. This didn't cause any issues, but would return the wrong role for recipients. | ||
|
||
In 0.2 a check is added to make sure the role of a mediation record matches with actions (e.g. a recipient can't grant mediation), which means it will throw an error if the role is not set correctly. | ||
|
||
Because it's not always possible detect whether the role should actually be mediator or recipient, a number of configuration options are provided on how the role should be updated using the `v0_1ToV0_2.mediationRoleUpdateStrategy` option: | ||
|
||
- `allMediator`: The role is set to `MediationRole.Mediator` for both mediators and recipients | ||
- `allRecipient`: The role is set to `MediationRole.Recipient` for both mediators and recipients | ||
- `recipientIfEndpoint` (**default**): The role is set to `MediationRole.Recipient` if their is an `endpoint` configured on the record. The endpoint is not set when running as a mediator. There is one case where this could be problematic when the role should be recipient, if the mediation grant hasn't actually occurred (meaning the endpoint is not set). This is probably the best approach | ||
otherwise it is set to `MediationRole.Mediator` | ||
- `doNotChange`: The role is not changed | ||
|
||
Most agents only act as either the role of mediator or recipient, in which case the `allMediator` or `allRecipient` configuration is the most appropriate. If your agent acts as both a recipient and mediator, the `recipientIfEndpoint` configuration is the most appropriate. The `doNotChange` options is not recommended and can lead to errors if the role is not set correctly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# Updating | ||
|
||
- [Update Strategies](#update-strategies) | ||
- [Backups](#backups) | ||
|
||
## Update Strategies | ||
|
||
There are three options on how to leverage the update assistant on agent startup: | ||
|
||
1. Manually instantiating the update assistant on agent startup | ||
2. Storing the agent storage version outside of the agent storage | ||
3. Automatically update on agent startup | ||
|
||
### Manually instantiating the update assistant on agent startup | ||
|
||
When the version of the storage is stored inside the agent storage, it means we need to check if the agent needs to be updated on every agent startup. We'll initialize the update assistant and check whether the storage is up to date. The advantage of this approach is that you don't have to store anything yourself, and have full control over the workflow. | ||
|
||
```ts | ||
import { UpdateAssistant, Agent } from '@aries-framework/core' | ||
|
||
// or @aries-framework/node | ||
import { agentDependencies } from '@aries-framework/react-native' | ||
|
||
// First create the agent | ||
const agent = new Agent(config, agentDependencies) | ||
|
||
// Then initialize the update assistant with the update config | ||
const updateAssistant = new UpdateAssistant(agent, { | ||
v0_1ToV0_2: { | ||
mediationRoleUpdateStrategy: 'allMediator', | ||
}, | ||
}) | ||
|
||
// Initialize the update assistant so we can read the current storage version | ||
// from the wallet. If you manually initialize the wallet you should do this _before_ | ||
// calling initialize on the update assistant | ||
// await agent.wallet.initialize(walletConfig) | ||
await updateAssistant.initialize() | ||
|
||
// Check if the agent is up to date, if not call update | ||
if (!(await updateAssistant.isUpToDate())) { | ||
await updateAssistant.update() | ||
} | ||
|
||
// Once finished initialize the agent. You should do this on every launch of the agent | ||
await agent.initialize() | ||
``` | ||
|
||
### Storing the agent storage version outside of the agent storage | ||
|
||
When the version of the storage is stored outside of the agent storage, you don't have to initialize the `UpdateAssistant` on every agent agent startup. You can just check if the storage version is up to date and instantiate the `UpdateAssistant` if not. The advantage of this approach is that you don't have to instantiate the `UpdateAssistant` on every agent startup, but this does mean that you have to store the storage version yourself. | ||
|
||
When a wallet has been exported and later imported you don't always have the latest version available. If this is the case you can always rely on Method 1 or 2 for updating the wallet, and storing the version yourself afterwards. You can also get the current version by calling `await updateAssistant.getCurrentAgentStorageVersion()`. Do note the `UpdateAssistant` needs to be initialized before calling this method. | ||
|
||
```ts | ||
import { UpdateAssistant, Agent } from '@aries-framework/core' | ||
|
||
// or @aries-framework/node | ||
import { agentDependencies } from '@aries-framework/react-native' | ||
|
||
// The storage version will normally be stored in e.g. persistent storage on a mobile device | ||
let currentStorageVersion: VersionString = '0.1' | ||
|
||
// First create the agent | ||
const agent = new Agent(config, agentDependencies) | ||
|
||
// We only initialize the update assistant if our stored version is not equal | ||
// to the frameworkStorageVersion of the UpdateAssistant. The advantage of this | ||
// is that we don't have to initialize the UpdateAssistant to retrieve the current | ||
// storage version. | ||
if (currentStorageVersion !== UpdateAssistant.frameworkStorageVersion) { | ||
const updateAssistant = new UpdateAssistant(agent, { | ||
v0_1ToV0_2: { | ||
mediationRoleUpdateStrategy: 'recipientIfEndpoint', | ||
}, | ||
}) | ||
|
||
// Same as with the previous strategy, if you normally call agent.wallet.initialize() manually | ||
// you need to call this before calling updateAssistant.initialize() | ||
await updateAssistant.initialize() | ||
|
||
await updateAssistant.update() | ||
|
||
// Store the version so we can leverage it during the next agent startup and don't have | ||
// to initialize the update assistant again until a new version is released | ||
currentStorageVersion = UpdateAssistant.frameworkStorageVersion | ||
} | ||
|
||
// Once finished initialize the agent. You should do this on every launch of the agent | ||
await agent.initialize() | ||
``` | ||
|
||
### Automatically update on agent startup | ||
|
||
This is by far the easiest way to update the agent, but has the least amount of flexibility and is not configurable. This means you will have to use the default update options to update the agent storage. You can find the default update config in the respective version migration guides (e.g. in [0.1-to-0.2](/docs/migration/0.1-to-0.2.md)). | ||
|
||
```ts | ||
import { UpdateAssistant, Agent } from '@aries-framework/core' | ||
|
||
// or @aries-framework/node | ||
import { agentDependencies } from '@aries-framework/react-native' | ||
|
||
// First create the agent, setting the autoUpdateStorageOnStartup option to true | ||
const agent = new Agent({ ...config, autoUpdateStorageOnStartup: true }, agentDependencies) | ||
|
||
// Then we call initialize, which under the hood will call the update assistant if the storage is not update to date. | ||
await agent.initialize() | ||
``` | ||
|
||
## Backups | ||
|
||
Before starting the update, the update assistant will automatically create a backup of the wallet. If the migration succeeds the backup won't be used. If the backup fails, another backup will be made of the migrated wallet, after which the backup will be restored. | ||
|
||
The backups can be found at the following locations. The `backupIdentifier` is generated at the start of the update process and generated based on the current timestamp. | ||
|
||
- Backup path: `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}` | ||
- Migration backup: `${agent.config.fileSystem.basePath}/afj/migration/backup/${backupIdentifier}-error` | ||
|
||
> In the future the backup assistant will make a number of improvements to the recovery process. Namely: | ||
> | ||
> - Do not throw an error if the update fails, but rather return an object that contains the status, and include the backup paths and backup identifiers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './didcomm' | ||
export * from './migration' |
Oops, something went wrong.