Skip to content

Commit

Permalink
feat(proofs): proofs module migration script for 0.3.0 (#1020)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <timo@animo.id>
  • Loading branch information
TimoGlastra authored Sep 22, 2022
1 parent 5a286b7 commit 5e9e0fc
Show file tree
Hide file tree
Showing 15 changed files with 1,996 additions and 88 deletions.
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
58 changes: 49 additions & 9 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,15 +95,38 @@ 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.')
Expand All @@ -96,6 +135,7 @@ export class UpdateAssistant<Agent extends BaseAgent = BaseAgent> {

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
95 changes: 59 additions & 36 deletions packages/core/src/storage/migration/__tests__/0.1.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { FileSystem } from '../../../../src'
import type { DidInfo, DidConfig } from '../../../wallet'
import type { V0_1ToV0_2UpdateConfig } from '../updates/0.1-0.2'

import { unlinkSync, readFileSync } from 'fs'
Expand All @@ -11,7 +10,6 @@ import { agentDependencies as dependencies } from '../../../../tests/helpers'
import { InjectionSymbols } from '../../../constants'
import { DependencyManager } from '../../../plugins'
import * as uuid from '../../../utils/uuid'
import { IndyWallet } from '../../../wallet/IndyWallet'
import { UpdateAssistant } from '../UpdateAssistant'

const backupDate = new Date('2022-01-21T22:50:20.522Z')
Expand All @@ -31,19 +29,6 @@ const mediationRoleUpdateStrategies: V0_1ToV0_2UpdateConfig['mediationRoleUpdate
]

describe('UpdateAssistant | v0.1 - v0.2', () => {
let createDidSpy: jest.SpyInstance<Promise<DidInfo>, [didConfig?: DidConfig | undefined]>

beforeAll(async () => {
// We need to mock did generation to create a consistent mediator routing record across sessions
createDidSpy = jest
.spyOn(IndyWallet.prototype, 'createDid')
.mockImplementation(async () => ({ did: 'mock-did', verkey: 'ocxwFbXouLkzuTCyyjFg1bPGK3nM6aPv1pZ6fn5RNgD' }))
})

afterAll(async () => {
createDidSpy.mockReset()
})

it(`should correctly update the role in the mediation record`, async () => {
const aliceMediationRecordsString = readFileSync(
path.join(__dirname, '__fixtures__/alice-4-mediators-0.1.json'),
Expand Down Expand Up @@ -77,18 +62,21 @@ 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([])

// MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here
delete storageService.records.MEDIATOR_ROUTING_RECORD
expect(storageService.records).toMatchSnapshot(mediationRoleUpdateStrategy)

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down Expand Up @@ -136,19 +124,22 @@ 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('0.2')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.2')).toEqual([])

expect(await updateAssistant.isUpToDate()).toBe(true)
expect(await updateAssistant.getNeededUpdates()).toEqual([])
// MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here
delete storageService.records.MEDIATOR_ROUTING_RECORD
expect(storageService.records).toMatchSnapshot()

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down Expand Up @@ -185,18 +176,34 @@ 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([])

// MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here
delete storageService.records.MEDIATOR_ROUTING_RECORD
expect(storageService.records).toMatchSnapshot()

// Need to remove backupFiles after each run so we don't get IOErrors
Expand Down Expand Up @@ -237,18 +244,34 @@ 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([])

// MEDIATOR_ROUTING_RECORD recipientKeys will be different every time, and is not what we're testing here
delete storageService.records.MEDIATOR_ROUTING_RECORD
expect(storageService.records).toMatchSnapshot()

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

0 comments on commit 5e9e0fc

Please sign in to comment.