Skip to content

Commit

Permalink
passes wallet passphrase from CLI to migrator (#5631)
Browse files Browse the repository at this point in the history
* passes wallet passphrase from CLI to migrator

adds an error type, EncryptedWalletMigrationError, and catches errors of that
type in the three commands that run migrations: 'migrations:start',
'migrations:revert', and 'start'

prompts user to enter wallet passphrase and passes passphrase through to
migrator

to support passing the passphrase down to the migrator in the 'start' command we
pass the passphrase through 'NodeUtils.waitForOpen' and the the node's 'openDB' method

supports implementing database migrations that handle encrypted wallet logic.
for example, throwing the EncryptedWalletMigrationError on an encrypted account
and using the wallet passphrase passed to 'migrate' to decrypt and re-encrypt
the wallet

* removes passphrase prompt from start command

displays error message to user instead

removes logic plumbing passphrase through openDB to migrator
  • Loading branch information
hughy authored Nov 19, 2024
1 parent b801bf6 commit f16a7c3
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 9 deletions.
37 changes: 35 additions & 2 deletions ironfish-cli/src/commands/migrations/revert.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { AccountDecryptionFailedError, EncryptedWalletMigrationError } from '@ironfish/sdk'
import { Flags } from '@oclif/core'
import { IronfishCommand } from '../../command'
import { inputPrompt } from '../../ui'

export class RevertCommand extends IronfishCommand {
static description = `revert the last run migration`

static flags = {
passphrase: Flags.string({
description: 'Passphrase to unlock the wallet database with',
}),
}

static hidden = true

async start(): Promise<void> {
await this.parse(RevertCommand)
const { flags } = await this.parse(RevertCommand)

const node = await this.sdk.node()
await node.migrator.revert()

let walletPassphrase = flags.passphrase
// eslint-disable-next-line no-constant-condition
while (true) {
try {
await node.migrator.revert({ walletPassphrase })
break
} catch (e) {
if (
e instanceof EncryptedWalletMigrationError ||
e instanceof AccountDecryptionFailedError
) {
this.logger.info(e.message)
walletPassphrase = await inputPrompt(
'Enter your passphrase to unlock the wallet',
true,
{
password: true,
},
)
} else {
throw e
}
}
}
}
}
31 changes: 30 additions & 1 deletion ironfish-cli/src/commands/migrations/start.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { AccountDecryptionFailedError, EncryptedWalletMigrationError } from '@ironfish/sdk'
import { Flags } from '@oclif/core'
import { IronfishCommand } from '../../command'
import { inputPrompt } from '../../ui'

export class StartCommand extends IronfishCommand {
static description = `run migrations`
Expand All @@ -17,12 +19,39 @@ export class StartCommand extends IronfishCommand {
char: 'q',
default: false,
}),
passphrase: Flags.string({
description: 'Passphrase to unlock the wallet database with',
}),
}

async start(): Promise<void> {
const { flags } = await this.parse(StartCommand)

const node = await this.sdk.node()
await node.migrator.migrate({ quiet: flags.quiet, dryRun: flags.dry })

let walletPassphrase = flags.passphrase
// eslint-disable-next-line no-constant-condition
while (true) {
try {
await node.migrator.migrate({ quiet: flags.quiet, dryRun: flags.dry, walletPassphrase })
break
} catch (e) {
if (
e instanceof EncryptedWalletMigrationError ||
e instanceof AccountDecryptionFailedError
) {
this.logger.info(e.message)
walletPassphrase = await inputPrompt(
'Enter your passphrase to unlock the wallet',
true,
{
password: true,
},
)
} else {
throw e
}
}
}
}
}
22 changes: 20 additions & 2 deletions ironfish-cli/src/commands/start.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { Assert, FullNode, NodeUtils, PromiseUtils } from '@ironfish/sdk'
import {
Assert,
EncryptedWalletMigrationError,
FullNode,
NodeUtils,
PromiseUtils,
} from '@ironfish/sdk'
import { Flags } from '@oclif/core'
import inspector from 'node:inspector'
import { v4 as uuid } from 'uuid'
Expand Down Expand Up @@ -223,7 +229,19 @@ export default class Start extends IronfishCommand {
}
this.log(` `)

await NodeUtils.waitForOpen(node, () => this.closing)
try {
await NodeUtils.waitForOpen(node, () => this.closing)
} catch (e) {
if (e instanceof EncryptedWalletMigrationError) {
this.logger.error(e.message)
this.logger.error(
'Run `ironfish migrations:start` to enter wallet passphrase and migrate wallet databases',
)
this.exit(1)
} else {
throw e
}
}

if (this.closing) {
return startDoneResolve()
Expand Down
7 changes: 7 additions & 0 deletions ironfish/src/migrations/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

export class EncryptedWalletMigrationError extends Error {
name = this.constructor.name
}
1 change: 1 addition & 0 deletions ironfish/src/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

export * from './errors'
export { Migrator } from './migrator'
2 changes: 2 additions & 0 deletions ironfish/src/migrations/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export abstract class Migration {
tx: IDatabaseTransaction | undefined,
logger: Logger,
dryRun: boolean,
walletPassphrase: string | undefined,
): Promise<void>

abstract backward(
Expand All @@ -53,5 +54,6 @@ export abstract class Migration {
tx: IDatabaseTransaction | undefined,
logger: Logger,
dryRun: boolean,
walletPassphrase: string | undefined,
): Promise<void>
}
21 changes: 18 additions & 3 deletions ironfish/src/migrations/migrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class Migrator {
}
}

async revert(options?: { dryRun?: boolean }): Promise<void> {
async revert(options?: { dryRun?: boolean; walletPassphrase?: string }): Promise<void> {
const dryRun = options?.dryRun ?? false

const migrations = this.migrations.slice().reverse()
Expand All @@ -88,7 +88,14 @@ export class Migrator {
await db.open()
tx = db.transaction()

await migration.backward(this.context, db, tx, childLogger, dryRun)
await migration.backward(
this.context,
db,
tx,
childLogger,
dryRun,
options?.walletPassphrase,
)
await db.putVersion(migration.id - 1, tx)

if (dryRun) {
Expand All @@ -109,6 +116,7 @@ export class Migrator {
quiet?: boolean
quietNoop?: boolean
dryRun?: boolean
walletPassphrase?: string
}): Promise<void> {
const dryRun = options?.dryRun ?? false
const logger = this.logger.create({})
Expand Down Expand Up @@ -154,7 +162,14 @@ export class Migrator {
tx = db.transaction()
}

await migration.forward(this.context, db, tx, childLogger, dryRun)
await migration.forward(
this.context,
db,
tx,
childLogger,
dryRun,
options?.walletPassphrase,
)
await db.putVersion(migration.id, tx)

if (dryRun) {
Expand Down
5 changes: 4 additions & 1 deletion ironfish/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,10 @@ export class FullNode {
const initial = await this.migrator.isInitial()

if (migrate || initial) {
await this.migrator.migrate({ quiet: !migrate, quietNoop: true })
await this.migrator.migrate({
quiet: !migrate,
quietNoop: true,
})
}

try {
Expand Down
1 change: 1 addition & 0 deletions ironfish/src/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { JsonEncoder } from './exporter/encoders/json'
export * from './validator'
export * from './walletdb/walletdb'
export * from './multisig'
export * from './errors'

0 comments on commit f16a7c3

Please sign in to comment.