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

feat(proofs): proofs module migration script for 0.3.0 #1020

5 changes: 2 additions & 3 deletions packages/core/src/modules/proofs/repository/ProofRecord.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { TagsBase } from '../../../storage/BaseRecord'
import type { AutoAcceptProof } from '../models/ProofAutoAcceptType'
import type { ProofProtocolVersion } from '../models/ProofProtocolVersion'
import type { ProofState } from '../models/ProofState'

import { AriesFrameworkError } from '../../../error'
Expand All @@ -10,7 +9,7 @@ import { uuid } from '../../../utils/uuid'
export interface ProofRecordProps {
id?: string
createdAt?: Date
protocolVersion: ProofProtocolVersion
protocolVersion: string
isVerified?: boolean
state: ProofState
connectionId?: string
Expand All @@ -34,7 +33,7 @@ export type DefaultProofTags = {
export class ProofRecord extends BaseRecord<DefaultProofTags, CustomProofTags> {
public connectionId?: string
public threadId!: string
public protocolVersion!: ProofProtocolVersion
public protocolVersion!: string
public parentThreadId?: string
public isVerified?: boolean
public state!: ProofState
Expand Down
14 changes: 10 additions & 4 deletions packages/core/src/storage/migration/StorageUpdateService.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { AgentContext } from '../../agent'
import type { VersionString } from '../../utils/version'
import type { UpdateToVersion } from './updates'

import { InjectionSymbols } from '../../constants'
import { Logger } from '../../logger'
import { injectable, inject } from '../../plugins'
import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version'

import { StorageVersionRecord } from './repository/StorageVersionRecord'
import { StorageVersionRepository } from './repository/StorageVersionRepository'
Expand All @@ -24,10 +26,14 @@ export class StorageUpdateService {
this.storageVersionRepository = storageVersionRepository
}

public async isUpToDate(agentContext: AgentContext) {
const currentStorageVersion = await this.getCurrentStorageVersion(agentContext)
public async isUpToDate(agentContext: AgentContext, updateToVersion?: UpdateToVersion) {
const currentStorageVersion = parseVersionString(await this.getCurrentStorageVersion(agentContext))

const isUpToDate = CURRENT_FRAMEWORK_STORAGE_VERSION === currentStorageVersion
const compareToVersion = parseVersionString(updateToVersion ?? CURRENT_FRAMEWORK_STORAGE_VERSION)

const isUpToDate =
isFirstVersionEqualToSecond(currentStorageVersion, compareToVersion) ||
isFirstVersionHigherThanSecond(currentStorageVersion, compareToVersion)

return isUpToDate
}
Expand Down Expand Up @@ -65,7 +71,7 @@ export class StorageUpdateService {
* Retrieve the update record, creating it if it doesn't exist already.
*
* The storageVersion will be set to the INITIAL_STORAGE_VERSION if it doesn't exist yet,
* as we can assume the wallet was created before the udpate record existed
* as we can assume the wallet was created before the update record existed
*/
public async getStorageVersionRecord(agentContext: AgentContext) {
let storageVersionRecord = await this.storageVersionRepository.findById(
Expand Down
60 changes: 50 additions & 10 deletions packages/core/src/storage/migration/UpdateAssistant.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { BaseAgent } from '../../agent/BaseAgent'
import type { FileSystem } from '../FileSystem'
import type { UpdateConfig } from './updates'
import type { UpdateConfig, UpdateToVersion } from './updates'

import { InjectionSymbols } from '../../constants'
import { AriesFrameworkError } from '../../error'
import { isIndyError } from '../../utils/indyError'
import { isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version'
import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version'
import { WalletError } from '../../wallet/error/WalletError'

import { StorageUpdateService } from './StorageUpdateService'
Expand Down Expand Up @@ -43,8 +43,8 @@ export class UpdateAssistant<Agent extends BaseAgent = BaseAgent> {
}
}

public async isUpToDate() {
return this.storageUpdateService.isUpToDate(this.agent.context)
public async isUpToDate(updateToVersion?: UpdateToVersion) {
return this.storageUpdateService.isUpToDate(this.agent.context, updateToVersion)
}

public async getCurrentAgentStorageVersion() {
Expand All @@ -55,18 +55,34 @@ export class UpdateAssistant<Agent extends BaseAgent = BaseAgent> {
return CURRENT_FRAMEWORK_STORAGE_VERSION
}

public async getNeededUpdates() {
public async getNeededUpdates(toVersion?: UpdateToVersion) {
const currentStorageVersion = parseVersionString(
await this.storageUpdateService.getCurrentStorageVersion(this.agent.context)
)

const parsedToVersion = toVersion ? parseVersionString(toVersion) : undefined

// If the current storage version is higher or equal to the toVersion, we can't update, so return empty array
if (
parsedToVersion &&
(isFirstVersionHigherThanSecond(currentStorageVersion, parsedToVersion) ||
isFirstVersionEqualToSecond(currentStorageVersion, parsedToVersion))
) {
return []
}

// Filter updates. We don't want older updates we already applied
// or aren't needed because the wallet was created after the update script was made
const neededUpdates = supportedUpdates.filter((update) => {
const toVersion = parseVersionString(update.toVersion)
const updateToVersion = parseVersionString(update.toVersion)

// If the update toVersion is higher than the wanted toVersion, we skip the update
if (parsedToVersion && isFirstVersionHigherThanSecond(updateToVersion, parsedToVersion)) {
return false
}

// if an update toVersion is higher than currentStorageVersion we want to to include the update
return isFirstVersionHigherThanSecond(toVersion, currentStorageVersion)
return isFirstVersionHigherThanSecond(updateToVersion, currentStorageVersion)
})

// The current storage version is too old to update
Expand All @@ -79,23 +95,47 @@ export class UpdateAssistant<Agent extends BaseAgent = BaseAgent> {
)
}

const lastUpdateToVersion = neededUpdates.length > 0 ? neededUpdates[neededUpdates.length - 1].toVersion : undefined
if (toVersion && lastUpdateToVersion && lastUpdateToVersion !== toVersion) {
throw new AriesFrameworkError(
`No update found for toVersion ${toVersion}. Make sure the toVersion is a valid version you can update to`
)
}

return neededUpdates
}

public async update() {
public async update(updateToVersion?: UpdateToVersion) {
const updateIdentifier = Date.now().toString()

try {
this.agent.config.logger.info(`Starting update of agent storage with updateIdentifier ${updateIdentifier}`)
const neededUpdates = await this.getNeededUpdates()
const neededUpdates = await this.getNeededUpdates(updateToVersion)

const currentStorageVersion = parseVersionString(
await this.storageUpdateService.getCurrentStorageVersion(this.agent.context)
)
const parsedToVersion = updateToVersion ? parseVersionString(updateToVersion) : undefined

// If the current storage version is higher or equal to the toVersion, we can't update.
if (
parsedToVersion &&
(isFirstVersionHigherThanSecond(currentStorageVersion, parsedToVersion) ||
isFirstVersionEqualToSecond(currentStorageVersion, parsedToVersion))
) {
throw new StorageUpdateError(
`Can't update to version ${updateToVersion} because it is lower or equal to the current agent storage version ${currentStorageVersion[0]}.${currentStorageVersion[1]}}`
)
}

if (neededUpdates.length == 0) {
this.agent.config.logger.info('No update needed. Agent storage is up to date.')
this.agent.config.logger.info(`No update needed. Agent storage is up to date.`)
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
return
}

const fromVersion = neededUpdates[0].fromVersion
const toVersion = neededUpdates[neededUpdates.length - 1].toVersion

this.agent.config.logger.info(
`Starting update process. Total of ${neededUpdates.length} update(s) will be applied to update the agent storage from version ${fromVersion} to version ${toVersion}`
)
Expand Down
68 changes: 47 additions & 21 deletions packages/core/src/storage/migration/__tests__/0.1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,18 @@ describe('UpdateAssistant | v0.1 - v0.2', () => {
// is opened as an existing wallet instead of a new wallet
storageService.records = JSON.parse(aliceMediationRecordsString)

expect(await updateAssistant.getNeededUpdates()).toEqual([
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([
{
fromVersion: '0.1',
toVersion: '0.2',
doUpdate: expect.any(Function),
},
])

await updateAssistant.update()
await updateAssistant.update('0.2')

expect(await updateAssistant.isUpToDate()).toBe(true)
expect(await updateAssistant.getNeededUpdates()).toEqual([])
expect(await updateAssistant.isUpToDate('0.2')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([])
expect(storageService.records).toMatchSnapshot(mediationRoleUpdateStrategy)

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down Expand Up @@ -136,19 +136,19 @@ describe('UpdateAssistant | v0.1 - v0.2', () => {
// is opened as an existing wallet instead of a new wallet
storageService.records = JSON.parse(aliceCredentialRecordsString)

expect(await updateAssistant.isUpToDate()).toBe(false)
expect(await updateAssistant.getNeededUpdates()).toEqual([
expect(await updateAssistant.isUpToDate('0.2')).toBe(false)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([
{
fromVersion: '0.1',
toVersion: '0.2',
doUpdate: expect.any(Function),
},
])

await updateAssistant.update()
await updateAssistant.update('0.2')

expect(await updateAssistant.isUpToDate()).toBe(true)
expect(await updateAssistant.getNeededUpdates()).toEqual([])
expect(await updateAssistant.isUpToDate('0.2')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([])
expect(storageService.records).toMatchSnapshot()

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down Expand Up @@ -185,18 +185,31 @@ describe('UpdateAssistant | v0.1 - v0.2', () => {

const fileSystem = agent.injectionContainer.resolve<FileSystem>(InjectionSymbols.FileSystem)

// We need to manually initialize the wallet as we're using the in memory wallet service
// When we call agent.initialize() it will create the wallet and store the current framework
// version in the in memory storage service. We need to manually set the records between initializing
// the wallet and calling agent.initialize()
await agent.wallet.initialize(walletConfig)
const updateAssistant = new UpdateAssistant(agent, {
v0_1ToV0_2: {
mediationRoleUpdateStrategy: 'doNotChange',
},
})

await updateAssistant.initialize()

// Set storage after initialization. This mimics as if this wallet
// is opened as an existing wallet instead of a new wallet
storageService.records = JSON.parse(aliceCredentialRecordsString)

await agent.initialize()
expect(await updateAssistant.isUpToDate('0.2')).toBe(false)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([
{
fromVersion: '0.1',
toVersion: '0.2',
doUpdate: expect.any(Function),
},
])

await updateAssistant.update('0.2')

expect(await updateAssistant.isUpToDate('0.2')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([])
expect(storageService.records).toMatchSnapshot()

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down Expand Up @@ -237,18 +250,31 @@ describe('UpdateAssistant | v0.1 - v0.2', () => {

const fileSystem = agent.injectionContainer.resolve<FileSystem>(InjectionSymbols.FileSystem)

// We need to manually initialize the wallet as we're using the in memory wallet service
// When we call agent.initialize() it will create the wallet and store the current framework
// version in the in memory storage service. We need to manually set the records between initializing
// the wallet and calling agent.initialize()
await agent.wallet.initialize(walletConfig)
const updateAssistant = new UpdateAssistant(agent, {
v0_1ToV0_2: {
mediationRoleUpdateStrategy: 'doNotChange',
},
})

await updateAssistant.initialize()

// Set storage after initialization. This mimics as if this wallet
// is opened as an existing wallet instead of a new wallet
storageService.records = JSON.parse(aliceConnectionRecordsString)

await agent.initialize()
expect(await updateAssistant.isUpToDate('0.2')).toBe(false)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([
{
fromVersion: '0.1',
toVersion: '0.2',
doUpdate: expect.any(Function),
},
])

await updateAssistant.update('0.2')

expect(await updateAssistant.isUpToDate('0.2')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([])
expect(storageService.records).toMatchSnapshot()

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down
Loading