From c4ebd5b5a81a766e917190113445ab81e5b0aac5 Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 6 Jul 2023 10:48:32 -0700 Subject: [PATCH 01/37] feat(ifl-1237): generic account encoder (#4028) --- .../wallet/account/encoder/account.test.ts | 24 ++++++++ .../src/wallet/account/encoder/account.ts | 56 +++++++++++++++++++ .../src/wallet/account/encoder/bech32.test.ts | 4 +- ironfish/src/wallet/account/encoder/bech32.ts | 2 +- .../src/wallet/account/encoder/encoder.ts | 7 +++ ironfish/src/wallet/account/encoder/json.ts | 29 ++++++++-- .../src/wallet/account/encoder/mnemonic.ts | 2 +- 7 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 ironfish/src/wallet/account/encoder/account.test.ts create mode 100644 ironfish/src/wallet/account/encoder/account.ts diff --git a/ironfish/src/wallet/account/encoder/account.test.ts b/ironfish/src/wallet/account/encoder/account.test.ts new file mode 100644 index 0000000000..b62592cf41 --- /dev/null +++ b/ironfish/src/wallet/account/encoder/account.test.ts @@ -0,0 +1,24 @@ +/* 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 } from '../../../assert' +import { decodeAccount, encodeAccount } from './account' +import { Format } from './encoder' + +describe('decodeAccount/encodeAccount', () => { + describe('when decoding/encoding', () => { + it('decodes arbitrary format without failure', () => { + const jsonString = + '{"version":2,"name":"ffff","spendingKey":"9e02be4c932ebc09c1eba0273a0ea41344615097222a5fb8a8787fba0db1a8fa","viewKey":"8d027bae046d73cf0be07e6024dd5719fb3bbdcac21cbb54b9850f6e4f89cd28fdb49856e5272870e497d65b177682f280938e379696dbdc689868eee5e52c1f","incomingViewKey":"348bd554fa8f1dc9686146ced3d483c48321880fc1a6cf323981bb2a41f99700","outgoingViewKey":"68543a20edaa435fb49155d1defb5141426c84d56728a8c5ae7692bc07875e3b","publicAddress":"471325ab136b883fe3dacff0f288153a9669dd4bae3d73b6578b33722a3bd22c","createdAt":{"hash":"000000000000007e3b8229e5fa28ecf70d7a34c973dd67b87160d4e55275a907","sequence":97654}}' + const decoded = decodeAccount(jsonString) + Assert.isNotNull(decoded) + const encoded = encodeAccount(decoded, Format.JSON) + expect(encoded).toEqual(jsonString) + }) + it('throws when json is not a valid account', () => { + const invalidJson = '{}' + expect(() => decodeAccount(invalidJson)).toThrow() + }) + }) +}) diff --git a/ironfish/src/wallet/account/encoder/account.ts b/ironfish/src/wallet/account/encoder/account.ts new file mode 100644 index 0000000000..cf24240f13 --- /dev/null +++ b/ironfish/src/wallet/account/encoder/account.ts @@ -0,0 +1,56 @@ +/* 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 } from '../../../assert' +import { AccountImport } from '../../walletdb/accountValue' +import { Bech32Encoder } from './bech32' +import { Bech32JsonEncoder } from './bech32json' +import { AccountDecodingOptions, AccountEncodingOptions, Format } from './encoder' +import { JsonEncoder } from './json' +import { MnemonicEncoder } from './mnemonic' +import { SpendingKeyEncoder } from './spendingKey' + +const ENCODER_VERSIONS = [ + JsonEncoder, + MnemonicEncoder, + SpendingKeyEncoder, + Bech32JsonEncoder, + Bech32Encoder, +] + +export function encodeAccount( + value: AccountImport, + format: Format, + options: AccountEncodingOptions = {}, +): string { + switch (format) { + case Format.JSON: + return new JsonEncoder().encode(value) + case Format.Bech32: + return new Bech32Encoder().encode(value) + case Format.SpendingKey: + return new SpendingKeyEncoder().encode(value) + case Format.Mnemonic: + return new MnemonicEncoder().encode(value, options) + default: + return Assert.isUnreachable(format) + } +} + +export function decodeAccount( + value: string, + options: AccountDecodingOptions = {}, +): AccountImport { + let decoded = null + for (const encoder of ENCODER_VERSIONS) { + try { + decoded = new encoder().decode(value, options) + } catch (e) { + continue + } + if (decoded) { + return decoded + } + } + throw new Error('Account could not be decoded') +} diff --git a/ironfish/src/wallet/account/encoder/bech32.test.ts b/ironfish/src/wallet/account/encoder/bech32.test.ts index 554039ce58..8833950210 100644 --- a/ironfish/src/wallet/account/encoder/bech32.test.ts +++ b/ironfish/src/wallet/account/encoder/bech32.test.ts @@ -5,11 +5,11 @@ import { generateKey } from '@ironfish/rust-nodejs' import { Bech32m } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' -import { BECH32_ACCOUNT_PREFIX, Bech32AccountEncoder } from './bech32' +import { BECH32_ACCOUNT_PREFIX, Bech32Encoder } from './bech32' describe('Bech32AccountEncoder', () => { const key = generateKey() - const encoder = new Bech32AccountEncoder() + const encoder = new Bech32Encoder() it('encodes the account as a bech32 string and decodes the string', () => { const accountImport: AccountImport = { diff --git a/ironfish/src/wallet/account/encoder/bech32.ts b/ironfish/src/wallet/account/encoder/bech32.ts index cf04607526..d341506b30 100644 --- a/ironfish/src/wallet/account/encoder/bech32.ts +++ b/ironfish/src/wallet/account/encoder/bech32.ts @@ -9,7 +9,7 @@ import { ACCOUNT_SCHEMA_VERSION } from '../account' import { AccountEncoder } from './encoder' export const BECH32_ACCOUNT_PREFIX = 'ifaccount' -export class Bech32AccountEncoder implements AccountEncoder { +export class Bech32Encoder implements AccountEncoder { VERSION = 1 encode(value: AccountImport): string { diff --git a/ironfish/src/wallet/account/encoder/encoder.ts b/ironfish/src/wallet/account/encoder/encoder.ts index 7cbd19acc3..0b1eec5fe0 100644 --- a/ironfish/src/wallet/account/encoder/encoder.ts +++ b/ironfish/src/wallet/account/encoder/encoder.ts @@ -4,6 +4,13 @@ import { LanguageKey } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' +export enum Format { + JSON = 'JSON', + Bech32 = 'Bech32', + SpendingKey = 'SpendingKey', + Mnemonic = 'Mnemonic', +} + export type AccountEncodingOptions = { language?: LanguageKey } diff --git a/ironfish/src/wallet/account/encoder/json.ts b/ironfish/src/wallet/account/encoder/json.ts index 4008a890e7..ef41b631ac 100644 --- a/ironfish/src/wallet/account/encoder/json.ts +++ b/ironfish/src/wallet/account/encoder/json.ts @@ -1,19 +1,36 @@ /* 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 { RpcAccountImport } from '../../../rpc/routes/wallet/types' import { validateAccount } from '../../validator' -import { AccountImport, AccountValue } from '../../walletdb/accountValue' +import { AccountImport } from '../../walletdb/accountValue' import { AccountEncoder } from './encoder' export class JsonEncoder implements AccountEncoder { encode(value: AccountImport): string { - return JSON.stringify(value) + let createdAt = null + if (value.createdAt) { + createdAt = { + hash: value.createdAt.hash.toString('hex'), + sequence: value.createdAt.sequence, + } + } + return JSON.stringify({ ...value, createdAt }) } decode(value: string): AccountImport { - const account = JSON.parse(value) as AccountImport - // TODO: consolidate AccountImport and AccountValue createdAt types - validateAccount(account as AccountValue) - return account + const account = JSON.parse(value) as RpcAccountImport + const updatedAccount = { + ...account, + createdAt: account.createdAt + ? { + hash: Buffer.from(account.createdAt.hash, 'hex'), + sequence: account.createdAt.sequence, + } + : null, + } as AccountImport + + validateAccount(updatedAccount) + return updatedAccount } } diff --git a/ironfish/src/wallet/account/encoder/mnemonic.ts b/ironfish/src/wallet/account/encoder/mnemonic.ts index 803159ca80..7280e56e83 100644 --- a/ironfish/src/wallet/account/encoder/mnemonic.ts +++ b/ironfish/src/wallet/account/encoder/mnemonic.ts @@ -14,7 +14,7 @@ import { ACCOUNT_SCHEMA_VERSION } from '../account' import { AccountDecodingOptions, AccountEncoder, AccountEncodingOptions } from './encoder' export class MnemonicEncoder implements AccountEncoder { - encode(value: AccountImport, options?: AccountEncodingOptions): string { + encode(value: AccountImport, options: AccountEncodingOptions): string { if (!value.spendingKey) { throw new EncodingError('Spending key is required for mnemonic key encoder') } From c78618cd236b4fc26cd9558cc0caad0b275ce015 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 6 Jul 2023 12:14:38 -0700 Subject: [PATCH 02/37] updates wallet exportAccount RPC to use encoders (#4031) adds 'format' and 'language' fields to requests to support encoding accounts on the node side instead of the client side updates 'account' response to return either an account object (for backwards compatibility) or an encoded string returns account object by default if no format is passed updates export RPC tests updates export CLI to accommodate updated response type --- ironfish-cli/src/commands/wallet/export.ts | 36 ++++++-- .../exportAccount.test.ts.fixture | 15 ++++ .../rpc/routes/wallet/exportAccount.test.ts | 90 ++++++++++++++++++- .../src/rpc/routes/wallet/exportAccount.ts | 65 ++++++-------- ironfish/src/rpc/routes/wallet/index.ts | 1 + 5 files changed, 159 insertions(+), 48 deletions(-) create mode 100644 ironfish/src/rpc/routes/wallet/__fixtures__/exportAccount.test.ts.fixture diff --git a/ironfish-cli/src/commands/wallet/export.ts b/ironfish-cli/src/commands/wallet/export.ts index a510263aab..a50ebc3e7c 100644 --- a/ironfish-cli/src/commands/wallet/export.ts +++ b/ironfish-cli/src/commands/wallet/export.ts @@ -2,7 +2,15 @@ * 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 { spendingKeyToWords } from '@ironfish/rust-nodejs' -import { Assert, Bech32m, ErrorUtils, LanguageKey, LanguageUtils } from '@ironfish/sdk' +import { + Assert, + Bech32m, + ErrorUtils, + LanguageKey, + LanguageUtils, + RpcAccountImportSchema, + YupUtils, +} from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' import fs from 'fs' import inquirer from 'inquirer' @@ -56,7 +64,7 @@ export class ExportCommand extends IronfishCommand { async start(): Promise { const { flags, args } = await this.parse(ExportCommand) const { color, local } = flags - const account = args.account as string + const accountName = args.account as string const exportPath = flags.path const viewOnly = flags.viewonly @@ -65,7 +73,21 @@ export class ExportCommand extends IronfishCommand { } const client = await this.sdk.connectRpc(local) - const response = await client.wallet.exportAccount({ account: account, viewOnly: viewOnly }) + const response = await client.wallet.exportAccount({ + account: accountName, + viewOnly: viewOnly, + }) + + const { result: account, error } = await YupUtils.tryValidate( + RpcAccountImportSchema, + response.content, + ) + + if (error) { + throw error + } + Assert.isNotNull(account) + let output if (flags.mnemonic) { @@ -98,10 +120,10 @@ export class ExportCommand extends IronfishCommand { languageCode = LanguageUtils.LANGUAGES[response.language] } Assert.isTruthy( - response.content.account.spendingKey, + account.spendingKey, 'The account you are trying to export does not have a spending key, therefore a mnemonic cannot be generated for it', ) - output = spendingKeyToWords(response.content.account.spendingKey, languageCode) + output = spendingKeyToWords(account.spendingKey, languageCode) } else if (flags.json) { output = JSON.stringify(response.content.account, undefined, ' ') @@ -119,7 +141,7 @@ export class ExportCommand extends IronfishCommand { const stats = await fs.promises.stat(resolved) if (stats.isDirectory()) { - resolved = this.sdk.fileSystem.join(resolved, `ironfish-${account}.txt`) + resolved = this.sdk.fileSystem.join(resolved, `ironfish-${accountName}.txt`) } if (fs.existsSync(resolved)) { @@ -135,7 +157,7 @@ export class ExportCommand extends IronfishCommand { } await fs.promises.writeFile(resolved, output) - this.log(`Exported account ${response.content.account.name} to ${resolved}`) + this.log(`Exported account ${account.name} to ${resolved}`) } catch (err: unknown) { if (ErrorUtils.isNoEntityError(err)) { await fs.promises.mkdir(path.dirname(resolved), { recursive: true }) diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/exportAccount.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/exportAccount.test.ts.fixture new file mode 100644 index 0000000000..d67538c137 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/exportAccount.test.ts.fixture @@ -0,0 +1,15 @@ +{ + "": [ + { + "version": 2, + "id": "1d8e4282-8a21-4902-83e8-182f7121367f", + "name": "test", + "spendingKey": "52030addd62125db3387702d9e71f45beabd3d4f1a394b8d4d44577386a06b3d", + "viewKey": "2d585c377bf9767fb14571e16d387015058487e23dc976611340558df853c72e18dafc4437e2c87939f6815a1c45f2e34cfcdd0a4db90f1427d386444ee3f34e", + "incomingViewKey": "48079a261db30f3edf7cf5a5eddf97717aa6064acdf46674ebb672a2af6db504", + "outgoingViewKey": "ccd5c786e7e57e923da21a154056d64a38f0bc2e2bfb827b36e5de3daf9f0bad", + "publicAddress": "182d10eafc1b1f51a996d112373e910a7e02d1dccc6760814558fdbe465ab323", + "createdAt": null + } + ] +} \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.test.ts b/ironfish/src/rpc/routes/wallet/exportAccount.test.ts index 1d57a972c6..eeefa3574a 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.test.ts @@ -2,15 +2,26 @@ * 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 { v4 as uuid } from 'uuid' +import { useAccountFixture } from '../../../testUtilities' import { createRouteTest } from '../../../testUtilities/routeTest' +import { Account } from '../../../wallet' +import { Bech32Encoder } from '../../../wallet/account/encoder/bech32' +import { Format } from '../../../wallet/account/encoder/encoder' +import { JsonEncoder } from '../../../wallet/account/encoder/json' +import { MnemonicEncoder } from '../../../wallet/account/encoder/mnemonic' +import { SpendingKeyEncoder } from '../../../wallet/account/encoder/spendingKey' import { ExportAccountResponse } from './exportAccount' describe('Route wallet/exportAccount', () => { const routeTest = createRouteTest(true) + let account: Account + + beforeAll(async () => { + account = await useAccountFixture(routeTest.node.wallet) + }) + it('should export a default account', async () => { - const account = await routeTest.node.wallet.createAccount(uuid(), true) const response = await routeTest.client .request('wallet/exportAccount', { account: account.name, @@ -33,7 +44,6 @@ describe('Route wallet/exportAccount', () => { }) it('should omit spending key when view only account is requested', async () => { - const account = await routeTest.node.wallet.createAccount(uuid(), true) const response = await routeTest.client .request('wallet/exportAccount', { account: account.name, @@ -54,4 +64,78 @@ describe('Route wallet/exportAccount', () => { }, }) }) + + it('should export an account as a json string if requested', async () => { + const response = await routeTest.client + .request('wallet/exportAccount', { + account: account.name, + format: Format.JSON, + }) + .waitForEnd() + + expect(response.status).toBe(200) + + const { id: _, ...accountImport } = account.serialize() + expect(response.content.account).toEqual(new JsonEncoder().encode(accountImport)) + }) + + it('should export an account as a bech32 string if requested', async () => { + const response = await routeTest.client + .request('wallet/exportAccount', { + account: account.name, + format: Format.Bech32, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content.account).toEqual(new Bech32Encoder().encode(account)) + }) + + it('should export an account as a spending key string if requested', async () => { + const response = await routeTest.client + .request('wallet/exportAccount', { + account: account.name, + format: Format.SpendingKey, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content.account).toEqual(new SpendingKeyEncoder().encode(account)) + }) + + it('should return an error when exporting a view only account in the spending key format', async () => { + await expect(() => + routeTest.client + .request('wallet/exportAccount', { + account: account.name, + format: Format.SpendingKey, + viewOnly: true, + }) + .waitForEnd(), + ).rejects.toThrow() + }) + + it('should export an account as a mnemonic phrase string if requested', async () => { + const response = await routeTest.client + .request('wallet/exportAccount', { + account: account.name, + format: Format.Mnemonic, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content.account).toEqual(new MnemonicEncoder().encode(account, {})) + }) + + it('should return an error when exporting a view only account in the mnemonic format', async () => { + await expect(() => + routeTest.client + .request('wallet/exportAccount', { + account: account.name, + format: Format.Mnemonic, + viewOnly: true, + }) + .waitForEnd(), + ).rejects.toThrow() + }) }) diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.ts b/ironfish/src/rpc/routes/wallet/exportAccount.ts index a0263cdb98..500e820a56 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.ts @@ -2,53 +2,35 @@ * 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 * as yup from 'yup' +import { LanguageKey, LanguageUtils } from '../../../utils' +import { encodeAccount } from '../../../wallet/account/encoder/account' +import { Format } from '../../../wallet/account/encoder/encoder' import { ApiNamespace, router } from '../router' +import { RpcAccountImport } from './types' import { getAccount } from './utils' -export type ExportAccountRequest = { account?: string; viewOnly?: boolean } +export type ExportAccountRequest = { + account?: string + viewOnly?: boolean + format?: Format + language?: LanguageKey +} export type ExportAccountResponse = { - account: { - name: string - spendingKey: string | null - viewKey: string - incomingViewKey: string - outgoingViewKey: string - publicAddress: string - version: number - createdAt: { - hash: string - sequence: number - } | null - } + account: string | RpcAccountImport | null } export const ExportAccountRequestSchema: yup.ObjectSchema = yup .object({ account: yup.string().trim(), viewOnly: yup.boolean().optional().default(false), + format: yup.string().oneOf(Object.values(Format)).optional(), + language: yup.string().oneOf(LanguageUtils.LANGUAGE_KEYS).optional(), }) .defined() export const ExportAccountResponseSchema: yup.ObjectSchema = yup .object({ - account: yup - .object({ - name: yup.string().defined(), - spendingKey: yup.string().nullable().defined(), - viewKey: yup.string().defined(), - incomingViewKey: yup.string().defined(), - outgoingViewKey: yup.string().defined(), - publicAddress: yup.string().defined(), - version: yup.number().defined(), - createdAt: yup - .object({ - hash: yup.string().defined(), - sequence: yup.number().defined(), - }) - .nullable() - .defined(), - }) - .defined(), + account: yup.mixed().nullable(), }) .defined() @@ -62,13 +44,20 @@ router.register( accountInfo.spendingKey = null } - let createdAt = null - if (accountInfo.createdAt) { - createdAt = { - hash: accountInfo.createdAt.hash.toString('hex'), - sequence: accountInfo.createdAt.sequence, + if (!request.data.format) { + let createdAt = null + if (accountInfo.createdAt) { + createdAt = { + hash: accountInfo.createdAt.hash.toString('hex'), + sequence: accountInfo.createdAt.sequence, + } } + request.end({ account: { ...accountInfo, createdAt } }) + } else { + const encoded = encodeAccount(accountInfo, request.data.format, { + language: request.data.language, + }) + request.end({ account: encoded }) } - request.end({ account: { ...accountInfo, createdAt } }) }, ) diff --git a/ironfish/src/rpc/routes/wallet/index.ts b/ironfish/src/rpc/routes/wallet/index.ts index 1797edf171..bee62e8959 100644 --- a/ironfish/src/rpc/routes/wallet/index.ts +++ b/ironfish/src/rpc/routes/wallet/index.ts @@ -26,4 +26,5 @@ export * from './rename' export * from './rescanAccount' export * from './sendTransaction' export * from './sendTransaction' +export * from './types' export * from './use' From 1875e945a004ae8fc0983016701b74848ed2c2aa Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 6 Jul 2023 13:13:42 -0700 Subject: [PATCH 03/37] feat(ifl-1234): account import rpc updates (#4030) * feat(ifl-1237): generic account encoder * feat(ifl-1234): account import updates to RPC * feat(ifl-1234): add tests for importAccount RPC --- .../rpc/routes/wallet/importAccount.test.ts | 114 +++++++++++++++++- .../src/rpc/routes/wallet/importAccount.ts | 35 +++--- ironfish/src/rpc/routes/wallet/utils.ts | 20 ++- .../src/wallet/account/encoder/bech32json.ts | 8 +- .../src/wallet/account/encoder/mnemonic.ts | 7 +- .../src/wallet/account/encoder/spendingKey.ts | 5 +- 6 files changed, 156 insertions(+), 33 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/importAccount.test.ts b/ironfish/src/rpc/routes/wallet/importAccount.test.ts index 29d746904a..392ca2e1d5 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.test.ts @@ -2,8 +2,11 @@ * 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 { generateKey } from '@ironfish/rust-nodejs' +import { generateKey, LanguageCode, spendingKeyToWords } from '@ironfish/rust-nodejs' import { createRouteTest } from '../../../testUtilities/routeTest' +import { encodeAccount } from '../../../wallet/account/encoder/account' +import { Bech32JsonEncoder } from '../../../wallet/account/encoder/bech32json' +import { Format } from '../../../wallet/account/encoder/encoder' import { ImportResponse } from './importAccount' describe('Route wallet/importAccount', () => { @@ -62,4 +65,113 @@ describe('Route wallet/importAccount', () => { isDefaultAccount: false, // This is false because the default account is already imported in a previous test }) }) + + describe('when importing string version of account', () => { + const createAccountImport = (name: string) => { + const key = generateKey() + const accountName = name + return { + name: accountName, + viewKey: key.viewKey, + spendingKey: key.spendingKey, + publicAddress: key.publicAddress, + incomingViewKey: key.incomingViewKey, + outgoingViewKey: key.outgoingViewKey, + version: 1, + createdAt: null, + } + } + + it('should import a string json encoded account', async () => { + const name = 'json' + const jsonString = encodeAccount(createAccountImport(name), Format.JSON) + + const response = await routeTest.client + .request('wallet/importAccount', { + account: jsonString, + rescan: false, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + isDefaultAccount: false, // This is false because the default account is already imported in a previous test + }) + }) + + it('should import a bech32json encoded account', async () => { + const name = 'bech32json' + const bech32Json = new Bech32JsonEncoder().encode(createAccountImport(name)) + + const response = await routeTest.client + .request('wallet/importAccount', { + account: bech32Json, + rescan: false, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + isDefaultAccount: false, // This is false because the default account is already imported in a previous test + }) + }) + + it('should import a bech32 encoded account', async () => { + const name = 'bech32' + const bech32 = encodeAccount(createAccountImport(name), Format.Bech32) + + const response = await routeTest.client + .request('wallet/importAccount', { + account: bech32, + rescan: false, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + isDefaultAccount: false, // This is false because the default account is already imported in a previous test + }) + }) + + it('should import a spending key encoded account', async () => { + const name = 'spendingKey' + const spendingKey = generateKey().spendingKey + + const response = await routeTest.client + .request('wallet/importAccount', { + account: spendingKey, + name: name, + rescan: false, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + isDefaultAccount: false, // This is false because the default account is already imported in a previous test + }) + }) + + it('should import a mnemonic key encoded account', async () => { + const name = 'mnemonic' + const mnemonic = spendingKeyToWords(generateKey().spendingKey, LanguageCode.English) + + const response = await routeTest.client + .request('wallet/importAccount', { + account: mnemonic, + name: name, + rescan: false, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content).toMatchObject({ + name: name, + isDefaultAccount: false, // This is false because the default account is already imported in a previous test + }) + }) + }) }) diff --git a/ironfish/src/rpc/routes/wallet/importAccount.ts b/ironfish/src/rpc/routes/wallet/importAccount.ts index 22ffe0282d..7b3191e5a7 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.ts @@ -3,13 +3,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { v4 as uuid } from 'uuid' import * as yup from 'yup' +import { decodeAccount } from '../../../wallet/account/encoder/account' import { ApiNamespace, router } from '../router' -import { RpcAccountImport, RpcAccountImportSchema } from './types' +import { RpcAccountImport } from './types' +import { deserializeRpcAccountImport } from './utils' export class ImportError extends Error {} export type ImportAccountRequest = { - account: RpcAccountImport + account: RpcAccountImport | string name?: string rescan?: boolean } @@ -23,7 +25,7 @@ export const ImportAccountRequestSchema: yup.ObjectSchema .object({ rescan: yup.boolean().optional().default(true), name: yup.string().optional(), - account: RpcAccountImportSchema, + account: yup.mixed().defined(), }) .defined() @@ -38,26 +40,19 @@ router.register( `${ApiNamespace.wallet}/importAccount`, ImportAccountRequestSchema, async (request, node): Promise => { - let createdAt = null - const name = request.data.account.name || request.data.name - if (!name) { - throw new ImportError('Account name is required') - } - - if (request.data.account.createdAt) { - createdAt = { - hash: Buffer.from(request.data.account.createdAt.hash, 'hex'), - sequence: request.data.account.createdAt.sequence, - } + let accountImport = null + if (typeof request.data.account === 'string') { + accountImport = decodeAccount(request.data.account, { + name: request.data.name, + }) + } else { + accountImport = deserializeRpcAccountImport(request.data.account) } - const accountValue = { + const account = await node.wallet.importAccount({ id: uuid(), - ...request.data.account, - name, - createdAt, - } - const account = await node.wallet.importAccount(accountValue) + ...accountImport, + }) if (request.data.rescan) { void node.wallet.scanTransactions() diff --git a/ironfish/src/rpc/routes/wallet/utils.ts b/ironfish/src/rpc/routes/wallet/utils.ts index 0ca336b0a4..a073a76a44 100644 --- a/ironfish/src/rpc/routes/wallet/utils.ts +++ b/ironfish/src/rpc/routes/wallet/utils.ts @@ -5,11 +5,17 @@ import { IronfishNode } from '../../../node' import { Note } from '../../../primitives' import { CurrencyUtils } from '../../../utils' import { Account } from '../../../wallet' +import { AccountImport } from '../../../wallet/walletdb/accountValue' import { AssetValue } from '../../../wallet/walletdb/assetValue' import { DecryptedNoteValue } from '../../../wallet/walletdb/decryptedNoteValue' import { TransactionValue } from '../../../wallet/walletdb/transactionValue' import { ValidationError } from '../../adapters' -import { RcpAccountAssetBalanceDelta, RpcAccountTransaction, RpcWalletNote } from './types' +import { + RcpAccountAssetBalanceDelta, + RpcAccountImport, + RpcAccountTransaction, + RpcWalletNote, +} from './types' export function getAccount(node: IronfishNode, name?: string): Account { if (name) { @@ -49,6 +55,18 @@ export function serializeRpcAccountTransaction( } } +export function deserializeRpcAccountImport(accountImport: RpcAccountImport): AccountImport { + return { + ...accountImport, + createdAt: accountImport.createdAt + ? { + hash: Buffer.from(accountImport.createdAt.hash, 'hex'), + sequence: accountImport.createdAt.sequence, + } + : null, + } +} + export async function getAssetBalanceDeltas( node: IronfishNode, transaction: TransactionValue, diff --git a/ironfish/src/wallet/account/encoder/bech32json.ts b/ironfish/src/wallet/account/encoder/bech32json.ts index 5e80c6d661..acd0fac511 100644 --- a/ironfish/src/wallet/account/encoder/bech32json.ts +++ b/ironfish/src/wallet/account/encoder/bech32json.ts @@ -1,23 +1,23 @@ /* 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 { EncodingError } from 'bufio' import { Bech32m } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' import { AccountEncoder } from './encoder' +import { JsonEncoder } from './json' export class Bech32JsonEncoder implements AccountEncoder { /** * @deprecated Bech32 JSON encoding is deprecated. Use the newest version of the Bech32JSONEncoder. */ encode(value: AccountImport): string { - return Bech32m.encode(JSON.stringify(value), 'ironfishaccount00000') + return Bech32m.encode(new JsonEncoder().encode(value), 'ironfishaccount00000') } decode(value: string): AccountImport { const [decoded, _] = Bech32m.decode(value) if (!decoded) { - throw new EncodingError('Invalid bech32 JSON encoding') + throw new Error('Invalid bech32 JSON encoding') } - return JSON.parse(decoded) as AccountImport + return new JsonEncoder().decode(decoded) } } diff --git a/ironfish/src/wallet/account/encoder/mnemonic.ts b/ironfish/src/wallet/account/encoder/mnemonic.ts index 7280e56e83..ee9dc4ad1f 100644 --- a/ironfish/src/wallet/account/encoder/mnemonic.ts +++ b/ironfish/src/wallet/account/encoder/mnemonic.ts @@ -7,7 +7,6 @@ import { spendingKeyToWords, wordsToSpendingKey, } from '@ironfish/rust-nodejs' -import { EncodingError } from 'bufio' import { LanguageUtils } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' @@ -16,7 +15,7 @@ import { AccountDecodingOptions, AccountEncoder, AccountEncodingOptions } from ' export class MnemonicEncoder implements AccountEncoder { encode(value: AccountImport, options: AccountEncodingOptions): string { if (!value.spendingKey) { - throw new EncodingError('Spending key is required for mnemonic key encoder') + throw new Error('Spending key is required for mnemonic key encoder') } return spendingKeyToWords( @@ -29,7 +28,7 @@ export class MnemonicEncoder implements AccountEncoder { decode(value: string, options: AccountDecodingOptions): AccountImport { if (!options.name) { - throw new EncodingError('Name option is required for mnemonic key encoder') + throw new Error('Name option is required for mnemonic key encoder') } let spendingKey = '' let language = null @@ -42,7 +41,7 @@ export class MnemonicEncoder implements AccountEncoder { language = LanguageUtils.languageCodeToKey(code) } if (language === null) { - throw new EncodingError('Invalid mnemonic') + throw new Error('Invalid mnemonic') } const key = generateKeyFromPrivateKey(spendingKey) return { diff --git a/ironfish/src/wallet/account/encoder/spendingKey.ts b/ironfish/src/wallet/account/encoder/spendingKey.ts index e718f926cf..4323ee22b3 100644 --- a/ironfish/src/wallet/account/encoder/spendingKey.ts +++ b/ironfish/src/wallet/account/encoder/spendingKey.ts @@ -2,7 +2,6 @@ * 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 { generateKeyFromPrivateKey } from '@ironfish/rust-nodejs' -import { EncodingError } from 'bufio' import { AccountImport } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' import { AccountDecodingOptions, AccountEncoder } from './encoder' @@ -10,14 +9,14 @@ import { AccountDecodingOptions, AccountEncoder } from './encoder' export class SpendingKeyEncoder implements AccountEncoder { encode(value: AccountImport): string { if (!value.spendingKey) { - throw new EncodingError('Spending key is required for spending key encoder') + throw new Error('Spending key is required for spending key encoder') } return value.spendingKey } decode(spendingKey: string, options: AccountDecodingOptions): AccountImport { if (!options.name) { - throw new EncodingError('Name option is required for spending key encoder') + throw new Error('Name option is required for spending key encoder') } const key = generateKeyFromPrivateKey(spendingKey) return { From e7b093f81fbf3f0a6af3080448950cc5e8fed43b Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 6 Jul 2023 14:02:31 -0700 Subject: [PATCH 04/37] feat(ifl-1263): support account renaming on decoding (#4035) --- .../wallet/account/encoder/account.test.ts | 21 +++++++++++++++++++ ironfish/src/wallet/account/encoder/bech32.ts | 6 +++--- .../src/wallet/account/encoder/bech32json.ts | 10 ++++++--- ironfish/src/wallet/account/encoder/json.ts | 6 +++--- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/ironfish/src/wallet/account/encoder/account.test.ts b/ironfish/src/wallet/account/encoder/account.test.ts index b62592cf41..29f5da81d3 100644 --- a/ironfish/src/wallet/account/encoder/account.test.ts +++ b/ironfish/src/wallet/account/encoder/account.test.ts @@ -4,6 +4,7 @@ import { Assert } from '../../../assert' import { decodeAccount, encodeAccount } from './account' +import { Bech32JsonEncoder } from './bech32json' import { Format } from './encoder' describe('decodeAccount/encodeAccount', () => { @@ -16,6 +17,26 @@ describe('decodeAccount/encodeAccount', () => { const encoded = encodeAccount(decoded, Format.JSON) expect(encoded).toEqual(jsonString) }) + + it('renames account when option is passed', () => { + const jsonString = + '{"version":2,"name":"ffff","spendingKey":"9e02be4c932ebc09c1eba0273a0ea41344615097222a5fb8a8787fba0db1a8fa","viewKey":"8d027bae046d73cf0be07e6024dd5719fb3bbdcac21cbb54b9850f6e4f89cd28fdb49856e5272870e497d65b177682f280938e379696dbdc689868eee5e52c1f","incomingViewKey":"348bd554fa8f1dc9686146ced3d483c48321880fc1a6cf323981bb2a41f99700","outgoingViewKey":"68543a20edaa435fb49155d1defb5141426c84d56728a8c5ae7692bc07875e3b","publicAddress":"471325ab136b883fe3dacff0f288153a9669dd4bae3d73b6578b33722a3bd22c","createdAt":{"hash":"000000000000007e3b8229e5fa28ecf70d7a34c973dd67b87160d4e55275a907","sequence":97654}}' + const decoded = decodeAccount(jsonString) + Assert.isNotNull(decoded) + + const encodedJson = encodeAccount(decoded, Format.JSON) + const decodedJson = decodeAccount(encodedJson, { name: 'new' }) + expect(decodedJson.name).toEqual('new') + + const encodedBech32 = encodeAccount(decoded, Format.Bech32) + const decodedBech32 = decodeAccount(encodedBech32, { name: 'new' }) + expect(decodedBech32.name).toEqual('new') + + const bech32Encoder = new Bech32JsonEncoder() + const encodedBech32Json = bech32Encoder.encode(decoded) + const decodedBech32Json = bech32Encoder.decode(encodedBech32Json, { name: 'new' }) + expect(decodedBech32Json.name).toEqual('new') + }) it('throws when json is not a valid account', () => { const invalidJson = '{}' expect(() => decodeAccount(invalidJson)).toThrow() diff --git a/ironfish/src/wallet/account/encoder/bech32.ts b/ironfish/src/wallet/account/encoder/bech32.ts index d341506b30..5769c052ea 100644 --- a/ironfish/src/wallet/account/encoder/bech32.ts +++ b/ironfish/src/wallet/account/encoder/bech32.ts @@ -6,7 +6,7 @@ import bufio from 'bufio' import { Bech32m } from '../../../utils' import { AccountImport, KEY_LENGTH, VIEW_KEY_LENGTH } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' -import { AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder } from './encoder' export const BECH32_ACCOUNT_PREFIX = 'ifaccount' export class Bech32Encoder implements AccountEncoder { @@ -36,7 +36,7 @@ export class Bech32Encoder implements AccountEncoder { return Bech32m.encode(bw.render().toString('hex'), BECH32_ACCOUNT_PREFIX) } - decode(value: string): AccountImport { + decode(value: string, options?: AccountDecodingOptions): AccountImport { const [hexEncoding, _] = Bech32m.decode(value) if (hexEncoding === null) { @@ -74,7 +74,7 @@ export class Bech32Encoder implements AccountEncoder { return { version: ACCOUNT_SCHEMA_VERSION, - name, + name: options?.name ? options.name : name, viewKey, incomingViewKey, outgoingViewKey, diff --git a/ironfish/src/wallet/account/encoder/bech32json.ts b/ironfish/src/wallet/account/encoder/bech32json.ts index acd0fac511..bd32b045a4 100644 --- a/ironfish/src/wallet/account/encoder/bech32json.ts +++ b/ironfish/src/wallet/account/encoder/bech32json.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Bech32m } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' -import { AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder } from './encoder' import { JsonEncoder } from './json' export class Bech32JsonEncoder implements AccountEncoder { /** @@ -13,11 +13,15 @@ export class Bech32JsonEncoder implements AccountEncoder { return Bech32m.encode(new JsonEncoder().encode(value), 'ironfishaccount00000') } - decode(value: string): AccountImport { + decode(value: string, options?: AccountDecodingOptions): AccountImport { const [decoded, _] = Bech32m.decode(value) if (!decoded) { throw new Error('Invalid bech32 JSON encoding') } - return new JsonEncoder().decode(decoded) + const accountImport = new JsonEncoder().decode(decoded) + return { + ...accountImport, + name: options?.name ? options.name : accountImport.name, + } } } diff --git a/ironfish/src/wallet/account/encoder/json.ts b/ironfish/src/wallet/account/encoder/json.ts index ef41b631ac..fcfdacd284 100644 --- a/ironfish/src/wallet/account/encoder/json.ts +++ b/ironfish/src/wallet/account/encoder/json.ts @@ -4,7 +4,7 @@ import { RpcAccountImport } from '../../../rpc/routes/wallet/types' import { validateAccount } from '../../validator' import { AccountImport } from '../../walletdb/accountValue' -import { AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder } from './encoder' export class JsonEncoder implements AccountEncoder { encode(value: AccountImport): string { @@ -18,10 +18,11 @@ export class JsonEncoder implements AccountEncoder { return JSON.stringify({ ...value, createdAt }) } - decode(value: string): AccountImport { + decode(value: string, options?: AccountDecodingOptions): AccountImport { const account = JSON.parse(value) as RpcAccountImport const updatedAccount = { ...account, + name: options?.name ? options.name : account.name, createdAt: account.createdAt ? { hash: Buffer.from(account.createdAt.hash, 'hex'), @@ -29,7 +30,6 @@ export class JsonEncoder implements AccountEncoder { } : null, } as AccountImport - validateAccount(updatedAccount) return updatedAccount } From 229ef9da057db0ed656fae21de8ae36e1a04688b Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Thu, 6 Jul 2023 12:50:05 -0700 Subject: [PATCH 05/37] Use `performance.now()` instead of `Date.now()` for performance measurements --- ironfish/src/metrics/meter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/metrics/meter.ts b/ironfish/src/metrics/meter.ts index 53eba104c6..419c2ed42f 100644 --- a/ironfish/src/metrics/meter.ts +++ b/ironfish/src/metrics/meter.ts @@ -129,7 +129,7 @@ export class Meter { } private update(): void { - const now = Date.now() + const now = performance.now() if (this._intervalLastMs === null) { this._intervalLastMs = now From 6ab42ad549e4ea1bd01232084074894828e17f1c Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:01:32 -0700 Subject: [PATCH 06/37] chore: Upgrade napi to latest version (#4006) We've been using a fairly old napi-rs version for quite a while due to bugs in some of the newer versions. Try upgrading to the latest version and see if we run into the same issues. This would unblock the GUI app from upgrading to a newer version of Electron, so we can officially sunset NodeJS 16 support, since 16 is going to be entering maintenance mode soon and we want to stay current with the LTS releases. --- Cargo.lock | 71 ++++++++++++++++++------------- ironfish-rust-nodejs/Cargo.toml | 9 +--- ironfish-rust-nodejs/index.js | 8 +++- ironfish-rust-nodejs/package.json | 2 +- yarn.lock | 8 ++-- 5 files changed, 56 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99dc48d4b0..189f8ba403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,6 +191,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "bitvec" version = "1.0.1" @@ -477,7 +483,7 @@ version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "bitflags", + "bitflags 1.3.2", "clap_lex", "indexmap", "textwrap", @@ -506,9 +512,12 @@ checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" [[package]] name = "convert_case" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation" @@ -668,12 +677,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.26" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +checksum = "1586fa608b1dab41f667475b4a41faec5ba680aee428bfa5de4ea520fdc6e901" dependencies = [ "quote", - "syn 1.0.107", + "syn 2.0.18", ] [[package]] @@ -1368,7 +1377,6 @@ dependencies = [ "napi", "napi-build", "napi-derive", - "napi-derive-backend", ] [[package]] @@ -1561,15 +1569,15 @@ dependencies = [ [[package]] name = "napi" -version = "2.9.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fece4c26c5132f8559080145fde9ba88700c0f1aa30a1ab3e057ab105814d" +checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e" dependencies = [ - "bitflags", + "bitflags 2.3.3", "ctor", + "napi-derive", "napi-sys", "once_cell", - "thread_local", ] [[package]] @@ -1580,10 +1588,11 @@ checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" [[package]] name = "napi-derive" -version = "2.9.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f3d8b02ef355898ea98f69082d9a183c8701c836942c2daf3e92364e88a0fa" +checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" dependencies = [ + "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", @@ -1593,15 +1602,16 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "1.0.38" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c35640513eb442fcbd1653a1c112fb6b2cc12b54d82f9c141f5859c721cab36" +checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" dependencies = [ "convert_case", "once_cell", "proc-macro2", "quote", "regex", + "semver", "syn 1.0.107", ] @@ -1702,7 +1712,7 @@ version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -2106,7 +2116,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2115,7 +2125,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2195,7 +2205,7 @@ version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -2254,7 +2264,7 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -2271,6 +2281,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.152" @@ -2457,15 +2473,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - [[package]] name = "threadpool" version = "1.8.1" @@ -2642,6 +2649,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-xid" version = "0.2.4" diff --git a/ironfish-rust-nodejs/Cargo.toml b/ironfish-rust-nodejs/Cargo.toml index b5e62a1152..169c090a9c 100644 --- a/ironfish-rust-nodejs/Cargo.toml +++ b/ironfish-rust-nodejs/Cargo.toml @@ -28,14 +28,9 @@ crate-type = ["cdylib"] [dependencies] base64 = "0.13.0" ironfish = { path = "../ironfish-rust" } -napi-derive = "=2.9.1" -# Pinning this can be removed if we eventually move to the latest releases again -napi-derive-backend = "=1.0.38" +napi = { version = "2.13.2", features = ["napi6"] } +napi-derive = "2.13.0" ironfish_mpc = { path = "../ironfish-mpc" } -[dependencies.napi] -version = "=2.9.1" -features = ["napi6"] - [build-dependencies] napi-build = "2.0.1" diff --git a/ironfish-rust-nodejs/index.js b/ironfish-rust-nodejs/index.js index 3835d7a6f7..fac746fc85 100644 --- a/ironfish-rust-nodejs/index.js +++ b/ironfish-rust-nodejs/index.js @@ -1,3 +1,9 @@ +/* tslint:disable */ +/* eslint-disable */ +/* prettier-ignore */ + +/* auto-generated by NAPI-RS */ + const { existsSync, readFileSync } = require('fs') const { join } = require('path') @@ -11,7 +17,7 @@ function isMusl() { // For Node 10 if (!process.report || typeof process.report.getReport !== 'function') { try { - const lddPath = require('child_process').execSync('which ldd').toString().trim(); + const lddPath = require('child_process').execSync('which ldd').toString().trim() return readFileSync(lddPath, 'utf8').includes('musl') } catch (e) { return true diff --git a/ironfish-rust-nodejs/package.json b/ironfish-rust-nodejs/package.json index 7d7bae7d34..753d101d0c 100644 --- a/ironfish-rust-nodejs/package.json +++ b/ironfish-rust-nodejs/package.json @@ -33,7 +33,7 @@ "node": ">=16" }, "devDependencies": { - "@napi-rs/cli": "2.14.3", + "@napi-rs/cli": "2.16.1", "@types/jest": "29.2.4", "jest": "29.3.1", "jest-jasmine2": "29.3.1", diff --git a/yarn.lock b/yarn.lock index d4ac5f5b07..7add9cbf92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2703,10 +2703,10 @@ "@napi-rs/blake-hash-win32-ia32-msvc" "1.3.3" "@napi-rs/blake-hash-win32-x64-msvc" "1.3.3" -"@napi-rs/cli@2.14.3": - version "2.14.3" - resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.14.3.tgz#fb2f554aaaf04f4583661b37149a38d9ee1045fc" - integrity sha512-yPc3dXHwynwR815s/7NczYx61wwmkZS1zlzgoVV+KaYJ9qAcd7pRiCZ7n/lBYL9lqUPWFZ7D9QYDn7b6teIsow== +"@napi-rs/cli@2.16.1": + version "2.16.1" + resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.16.1.tgz#912e1169be6ff8bb5e1e22bb702adcc5e73e232b" + integrity sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA== "@nodelib/fs.scandir@2.1.5": version "2.1.5" From 8d4d285531a32df4325e14f6924489fedc661ec4 Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Wed, 5 Jul 2023 08:42:51 -0700 Subject: [PATCH 07/37] Verified assets: implement persistent caching Store the data returned by the verified assets API in a file, so that the data does not need to be refetched at every restart of the node, but can be simply revalidated. --- ironfish/src/assets/assetsVerificationApi.ts | 36 ++++- ironfish/src/assets/assetsVerifier.test.ts | 150 ++++++++++++++++++- ironfish/src/assets/assetsVerifier.ts | 29 +++- ironfish/src/fileStores/fileStore.ts | 2 +- ironfish/src/fileStores/index.ts | 3 +- ironfish/src/fileStores/keyStore.ts | 7 + ironfish/src/fileStores/verifiedAssets.ts | 43 ++++++ ironfish/src/node.ts | 8 + 8 files changed, 270 insertions(+), 8 deletions(-) create mode 100644 ironfish/src/fileStores/verifiedAssets.ts diff --git a/ironfish/src/assets/assetsVerificationApi.ts b/ironfish/src/assets/assetsVerificationApi.ts index e5bad4f999..8e7f7588a8 100644 --- a/ironfish/src/assets/assetsVerificationApi.ts +++ b/ironfish/src/assets/assetsVerificationApi.ts @@ -19,6 +19,20 @@ export class VerifiedAssets { private readonly assetIds: Set = new Set() private lastModified?: string + export(): ExportedVerifiedAssets { + return { + assetIds: Array.from(this.assetIds), + lastModified: this.lastModified, + } + } + + static restore(options: ExportedVerifiedAssets): VerifiedAssets { + const verifiedAssets = new VerifiedAssets() + options.assetIds.forEach((identifier) => verifiedAssets.assetIds.add(identifier)) + verifiedAssets.lastModified = options.lastModified + return verifiedAssets + } + isVerified(assetId: Buffer | string): boolean { if (!(typeof assetId === 'string')) { assetId = assetId.toString('hex') @@ -27,6 +41,19 @@ export class VerifiedAssets { } } +// `ExportedVerifiedAssets` may seem redundant, given that it duplicates the +// same information in `VerifiedAssets`. However, it's needed to enable +// (de)serialization during caching. In particular, it solves the following +// issues: +// - `VerifiedAssets` is a class with methods, and the type-check logic as well +// as the serialization logic expect methods to be serialized. +// - The `assetIds` field from `VerifiedAssets` is a `Set`, which is not +// properly supported by the cache serializer. +export type ExportedVerifiedAssets = { + assetIds: string[] + lastModified?: string +} + export class AssetsVerificationApi { private readonly timeout: number @@ -43,7 +70,11 @@ export class AssetsVerificationApi { return verifiedAssets } - refreshVerifiedAssets(verifiedAssets: VerifiedAssets): Promise { + /** + * Queries the remote API for an updated version of `verifiedAssets`. + * @returns `true` if `verifiedAssets` has been updated; `false` otherwise, + */ + refreshVerifiedAssets(verifiedAssets: VerifiedAssets): Promise { const headers: GetVerifiedAssetsRequestHeaders = {} if (verifiedAssets['lastModified']) { headers['if-modified-since'] = verifiedAssets['lastModified'] @@ -63,11 +94,12 @@ export class AssetsVerificationApi { return verifiedAssets['assetIds'].add(identifier) }) verifiedAssets['lastModified'] = response.headers['last-modified'] + return true }, ) .catch((error: AxiosError) => { if (error.response?.status === 304) { - return + return false } throw error }) diff --git a/ironfish/src/assets/assetsVerifier.test.ts b/ironfish/src/assets/assetsVerifier.test.ts index 6ebc1bb850..72df16bfdc 100644 --- a/ironfish/src/assets/assetsVerifier.test.ts +++ b/ironfish/src/assets/assetsVerifier.test.ts @@ -2,6 +2,7 @@ * 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 nock from 'nock' +import { VerifiedAssetsCacheStore } from '../fileStores/verifiedAssets' import { AssetsVerifier } from './assetsVerifier' /* eslint-disable jest/no-standalone-expect */ @@ -103,7 +104,7 @@ describe('AssetsVerifier', () => { expect(refresh).toHaveBeenCalledTimes(1) }) - it('preserves cache after being stopped', async () => { + it('preserves the in-memory cache after being stopped', async () => { nock('https://test') .get('/assets/verified') .reply( @@ -223,7 +224,7 @@ describe('AssetsVerifier', () => { expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) }) - it('uses the cache when the API is unreachable', async () => { + it('uses the the in-memory cache when the API is unreachable', async () => { nock('https://test') .get('/assets/verified') .reply(200, { @@ -255,4 +256,149 @@ describe('AssetsVerifier', () => { expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) }) }) + + describe('with persistent cache', () => { + it('returns data from persistent cache', async () => { + const cache = Object.create( + VerifiedAssetsCacheStore.prototype, + ) as VerifiedAssetsCacheStore + cache.config = { + apiUrl: 'https://test/assets/verified', + assetIds: ['0123'], + } + + nock('https://test') + .get('/assets/verified') + .reply(200, { + data: [{ identifier: '4567' }], + }) + + const assetsVerifier = new AssetsVerifier({ + apiUrl: 'https://test/assets/verified', + cache: cache, + }) + const refresh = jest.spyOn(assetsVerifier as any, 'refresh') + + assetsVerifier.start() + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) + + await waitForRefreshToFinish(refresh) + }) + + it('ignores persistent cache if API url does not match', async () => { + const cache = Object.create( + VerifiedAssetsCacheStore.prototype, + ) as VerifiedAssetsCacheStore + cache.config = { + apiUrl: 'https://foo.test/assets/verified', + assetIds: ['0123'], + } + + nock('https://bar.test') + .get('/assets/verified') + .reply(200, { + data: [{ identifier: '4567' }], + }) + + const assetsVerifier = new AssetsVerifier({ + apiUrl: 'https://bar.test/assets/verified', + cache: cache, + }) + const refresh = jest.spyOn(assetsVerifier as any, 'refresh') + + assetsVerifier.start() + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unknown' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unknown' }) + + await waitForRefreshToFinish(refresh) + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unverified' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'verified' }) + }) + + it('saves the persistent cache after every update', async () => { + const cache = Object.create(VerifiedAssetsCacheStore.prototype) + const setManySpy = jest.spyOn(cache, 'setMany').mockReturnValue(undefined) + const saveSpy = jest.spyOn(cache, 'save').mockResolvedValue(undefined) + + nock('https://test') + .get('/assets/verified') + .reply(200, { + data: [{ identifier: '0123' }], + }) + .get('/assets/verified') + .reply( + 200, + { + data: [{ identifier: '4567' }], + }, + { 'last-modified': 'some-date' }, + ) + + const assetsVerifier = new AssetsVerifier({ + apiUrl: 'https://test/assets/verified', + cache: cache, + }) + const refresh = jest.spyOn(assetsVerifier as any, 'refresh') + + assetsVerifier.start() + await waitForRefreshToFinish(refresh) + + expect(setManySpy).toHaveBeenCalledWith({ + apiUrl: 'https://test/assets/verified', + assetIds: ['0123'], + lastModified: undefined, + }) + expect(saveSpy).toHaveBeenCalledTimes(1) + + jest.runOnlyPendingTimers() + await waitForRefreshToFinish(refresh) + + expect(setManySpy).toHaveBeenCalledWith({ + apiUrl: 'https://test/assets/verified', + assetIds: ['4567'], + lastModified: 'some-date', + }) + expect(saveSpy).toHaveBeenCalledTimes(2) + }) + + it('does not save the persistent cache after when not modified', async () => { + const cache = Object.create( + VerifiedAssetsCacheStore.prototype, + ) as VerifiedAssetsCacheStore + cache.config = { + apiUrl: 'https://test/assets/verified', + assetIds: ['0123'], + lastModified: 'some-date', + } + const setManySpy = jest.spyOn(cache, 'setMany').mockReturnValue(undefined) + const saveSpy = jest.spyOn(cache, 'save').mockResolvedValue(undefined) + + nock('https://test') + .matchHeader('if-modified-since', 'some-date') + .get('/assets/verified') + .reply(304) + + const assetsVerifier = new AssetsVerifier({ + apiUrl: 'https://test/assets/verified', + cache: cache, + }) + const refresh = jest.spyOn(assetsVerifier as any, 'refresh') + + assetsVerifier.start() + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) + + await waitForRefreshToFinish(refresh) + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) + expect(setManySpy).not.toHaveBeenCalled() + expect(saveSpy).not.toHaveBeenCalled() + }) + }) }) diff --git a/ironfish/src/assets/assetsVerifier.ts b/ironfish/src/assets/assetsVerifier.ts index c35dcbef03..2c6fb379fd 100644 --- a/ironfish/src/assets/assetsVerifier.ts +++ b/ironfish/src/assets/assetsVerifier.ts @@ -1,6 +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/. */ +import { VerifiedAssetsCacheStore } from '../fileStores/verifiedAssets' import { createRootLogger, Logger } from '../logger' import { ErrorUtils } from '../utils' import { SetIntervalToken } from '../utils' @@ -15,15 +16,25 @@ export class AssetsVerifier { private readonly logger: Logger private readonly api: AssetsVerificationApi + private readonly cache?: VerifiedAssetsCacheStore private started: boolean private refreshToken?: SetIntervalToken private verifiedAssets?: VerifiedAssets - constructor(options?: { apiUrl?: string; logger?: Logger }) { + constructor(options?: { + apiUrl?: string + cache?: VerifiedAssetsCacheStore + logger?: Logger + }) { this.logger = options?.logger ?? createRootLogger() this.api = new AssetsVerificationApi({ url: options?.apiUrl }) + this.cache = options?.cache this.started = false + + if (this.cache?.config?.apiUrl === this.api.url) { + this.verifiedAssets = VerifiedAssets.restore(this.cache.config) + } } start(): void { @@ -59,16 +70,30 @@ export class AssetsVerifier { try { if (this.verifiedAssets) { this.logger.debug(`Refreshing list of verified assets from ${this.api.url}`) - await this.api.refreshVerifiedAssets(this.verifiedAssets) + if (await this.api.refreshVerifiedAssets(this.verifiedAssets)) { + await this.saveCache() + } } else { this.logger.debug(`Downloading list of verified assets from ${this.api.url}`) this.verifiedAssets = await this.api.getVerifiedAssets() + await this.saveCache() } } catch (error) { this.logger.warn(`Error while fetching verified assets: ${ErrorUtils.renderError(error)}`) } } + private saveCache(): Promise { + if (!this.cache) { + return Promise.resolve() + } + this.cache.setMany({ + apiUrl: this.api.url, + ...(this.verifiedAssets ?? new VerifiedAssets()).export(), + }) + return this.cache.save() + } + verify(assetId: Buffer | string): AssetVerification { if (!this.started || !this.verifiedAssets) { return { status: 'unknown' } diff --git a/ironfish/src/fileStores/fileStore.ts b/ironfish/src/fileStores/fileStore.ts index 06160fa6b7..7dca79b4af 100644 --- a/ironfish/src/fileStores/fileStore.ts +++ b/ironfish/src/fileStores/fileStore.ts @@ -33,7 +33,7 @@ export class FileStore> { async save(data: PartialRecursive): Promise { const json = JSON.stringify(data, undefined, ' ') - await this.files.mkdir(this.dataDir, { recursive: true }) + await this.files.mkdir(path.dirname(this.configPath), { recursive: true }) await this.files.writeFile(this.configPath, json) } } diff --git a/ironfish/src/fileStores/index.ts b/ironfish/src/fileStores/index.ts index cf9bd9a9c3..0f371511cd 100644 --- a/ironfish/src/fileStores/index.ts +++ b/ironfish/src/fileStores/index.ts @@ -3,5 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ export * from './config' export * from './fileStore' -export * from './internal' export * from './hosts' +export * from './internal' +export * from './verifiedAssets' diff --git a/ironfish/src/fileStores/keyStore.ts b/ironfish/src/fileStores/keyStore.ts index 2c91f0169e..b000303935 100644 --- a/ironfish/src/fileStores/keyStore.ts +++ b/ironfish/src/fileStores/keyStore.ts @@ -139,6 +139,13 @@ export class KeyStore> { } } + setMany(params: Partial): void { + for (const key in params) { + const value = params[key] as TSchema[keyof TSchema] + this.set(key, value) + } + } + setOverride(key: T, value: TSchema[T]): void { const previousValue = this.config[key] diff --git a/ironfish/src/fileStores/verifiedAssets.ts b/ironfish/src/fileStores/verifiedAssets.ts new file mode 100644 index 0000000000..ee6cb1bf74 --- /dev/null +++ b/ironfish/src/fileStores/verifiedAssets.ts @@ -0,0 +1,43 @@ +/* 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 path from 'path' +import { ExportedVerifiedAssets } from '../assets' +import { FileSystem } from '../fileSystems' +import { createRootLogger, Logger } from '../logger' +import { ParseJsonError } from '../utils/json' +import { KeyStore } from './keyStore' + +export type VerifiedAssetsCacheOptions = { + apiUrl: string +} & ExportedVerifiedAssets + +export const VerifiedAssetsCacheOptionsDefaults: VerifiedAssetsCacheOptions = { + apiUrl: '', + assetIds: [], +} + +export const VERIFIED_ASSETS_CACHE_FILE_NAME = path.join('cache', 'verified-assets.json') + +export class VerifiedAssetsCacheStore extends KeyStore { + logger: Logger + + constructor(files: FileSystem, dataDir: string) { + super(files, VERIFIED_ASSETS_CACHE_FILE_NAME, VerifiedAssetsCacheOptionsDefaults, dataDir) + this.logger = createRootLogger() + } + + async load(): Promise { + try { + await super.load() + } catch (e) { + if (e instanceof ParseJsonError) { + this.logger.debug( + `Error: Could not parse JSON at ${this.storage.configPath}, ignoring.`, + ) + } else { + throw e + } + } + } +} diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index a4f2b14c81..2c0cc03f02 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -13,6 +13,7 @@ import { DEFAULT_DATA_DIR, HostsStore, InternalStore, + VerifiedAssetsCacheStore, } from './fileStores' import { FileSystem } from './fileSystems' import { createRootLogger, Logger } from './logger' @@ -73,6 +74,7 @@ export class IronfishNode { privateIdentity, hostsStore, networkId, + verifiedAssetsCache, }: { pkg: Package files: FileSystem @@ -89,6 +91,7 @@ export class IronfishNode { privateIdentity?: PrivateIdentity hostsStore: HostsStore networkId: number + verifiedAssetsCache: VerifiedAssetsCacheStore }) { this.files = files this.config = config @@ -174,6 +177,7 @@ export class IronfishNode { this.assetsVerifier = new AssetsVerifier({ apiUrl: config.get('assetVerificationApi'), + cache: verifiedAssetsCache, logger, }) @@ -221,6 +225,9 @@ export class IronfishNode { const hostsStore = new HostsStore(files, dataDir) await hostsStore.load() + const verifiedAssetsCache = new VerifiedAssetsCacheStore(files, dataDir) + await verifiedAssetsCache.load() + let workers = config.get('nodeWorkers') if (workers === -1) { workers = os.cpus().length - 1 @@ -308,6 +315,7 @@ export class IronfishNode { privateIdentity, hostsStore, networkId: networkDefinition.id, + verifiedAssetsCache, }) } From cb1525a2c676b94cd5f277b58b9f2e3030b0a294 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:22:42 -0700 Subject: [PATCH 08/37] updates export CLI for RPC changes (#4033) * updates export CLI for RPC changes removes account encoding logic from export CLI and uses the exportAccount RPC to encode accounts in the desired format instead * adds wallet/account/index.ts for account module exports --- ironfish-cli/src/commands/wallet/export.ts | 83 ++++------------------ ironfish/src/wallet/account/index.ts | 5 ++ ironfish/src/wallet/index.ts | 2 +- 3 files changed, 19 insertions(+), 71 deletions(-) create mode 100644 ironfish/src/wallet/account/index.ts diff --git a/ironfish-cli/src/commands/wallet/export.ts b/ironfish-cli/src/commands/wallet/export.ts index a50ebc3e7c..db06a2c0e7 100644 --- a/ironfish-cli/src/commands/wallet/export.ts +++ b/ironfish-cli/src/commands/wallet/export.ts @@ -1,19 +1,9 @@ /* 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 { spendingKeyToWords } from '@ironfish/rust-nodejs' -import { - Assert, - Bech32m, - ErrorUtils, - LanguageKey, - LanguageUtils, - RpcAccountImportSchema, - YupUtils, -} from '@ironfish/sdk' +import { ErrorUtils, Format, LanguageUtils } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' import fs from 'fs' -import inquirer from 'inquirer' import jsonColorizer from 'json-colorizer' import path from 'path' import { IronfishCommand } from '../../command' @@ -64,7 +54,7 @@ export class ExportCommand extends IronfishCommand { async start(): Promise { const { flags, args } = await this.parse(ExportCommand) const { color, local } = flags - const accountName = args.account as string + const account = args.account as string const exportPath = flags.path const viewOnly = flags.viewonly @@ -72,66 +62,19 @@ export class ExportCommand extends IronfishCommand { flags.mnemonic = true } + const format = flags.mnemonic ? Format.Mnemonic : flags.json ? Format.JSON : Format.Bech32 + const client = await this.sdk.connectRpc(local) const response = await client.wallet.exportAccount({ - account: accountName, - viewOnly: viewOnly, + account, + viewOnly, + format, + language: flags.language, }) - const { result: account, error } = await YupUtils.tryValidate( - RpcAccountImportSchema, - response.content, - ) - - if (error) { - throw error - } - Assert.isNotNull(account) - - let output - - if (flags.mnemonic) { - let languageCode = flags.language ? LanguageUtils.LANGUAGES[flags.language] : null - - if (languageCode == null) { - languageCode = LanguageUtils.inferLanguageCode() - - if (languageCode !== null) { - CliUx.ux.info( - `Detected Language as '${LanguageUtils.languageCodeToKey( - languageCode, - )}', exporting:`, - ) - } - } - - if (languageCode == null) { - CliUx.ux.info(`Could not detect your language, please select language for export`) - const response = await inquirer.prompt<{ - language: LanguageKey - }>([ - { - name: 'language', - message: `Select your language`, - type: 'list', - choices: LanguageUtils.LANGUAGE_KEYS, - }, - ]) - languageCode = LanguageUtils.LANGUAGES[response.language] - } - Assert.isTruthy( - account.spendingKey, - 'The account you are trying to export does not have a spending key, therefore a mnemonic cannot be generated for it', - ) - output = spendingKeyToWords(account.spendingKey, languageCode) - } else if (flags.json) { - output = JSON.stringify(response.content.account, undefined, ' ') - - if (color && flags.json && !exportPath) { - output = jsonColorizer(output) - } - } else { - output = Bech32m.encode(JSON.stringify(response.content.account), 'ironfishaccount00000') + let output = response.content.account as string + if (color && flags.json && !exportPath) { + output = jsonColorizer(output) } if (exportPath) { @@ -141,7 +84,7 @@ export class ExportCommand extends IronfishCommand { const stats = await fs.promises.stat(resolved) if (stats.isDirectory()) { - resolved = this.sdk.fileSystem.join(resolved, `ironfish-${accountName}.txt`) + resolved = this.sdk.fileSystem.join(resolved, `ironfish-${account}.txt`) } if (fs.existsSync(resolved)) { @@ -157,7 +100,7 @@ export class ExportCommand extends IronfishCommand { } await fs.promises.writeFile(resolved, output) - this.log(`Exported account ${account.name} to ${resolved}`) + this.log(`Exported account ${account} to ${resolved}`) } catch (err: unknown) { if (ErrorUtils.isNoEntityError(err)) { await fs.promises.mkdir(path.dirname(resolved), { recursive: true }) diff --git a/ironfish/src/wallet/account/index.ts b/ironfish/src/wallet/account/index.ts new file mode 100644 index 0000000000..6fff040fa7 --- /dev/null +++ b/ironfish/src/wallet/account/index.ts @@ -0,0 +1,5 @@ +/* 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 * from './account' +export { Format } from './encoder/encoder' diff --git a/ironfish/src/wallet/index.ts b/ironfish/src/wallet/index.ts index 022a335f26..d60f04290d 100644 --- a/ironfish/src/wallet/index.ts +++ b/ironfish/src/wallet/index.ts @@ -1,7 +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 * from './account/account' +export * from './account' export * from './wallet' export { AccountValue } from './walletdb/accountValue' export * from './validator' From 5e6ff5b71b47f7bb2b3a36dc126565af34be43b4 Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 6 Jul 2023 15:29:11 -0700 Subject: [PATCH 09/37] feat(ifl-1236): cli string account import (#4032) * feat(ifl-1237): generic account encoder * feat(ifl-1234): account import updates to RPC * feat(ifl-1236): import account cli simplification --- ironfish-cli/src/commands/wallet/import.ts | 153 +++------------------ 1 file changed, 18 insertions(+), 135 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/import.ts b/ironfish-cli/src/commands/wallet/import.ts index b4c106cb33..d725f27ebe 100644 --- a/ironfish-cli/src/commands/wallet/import.ts +++ b/ironfish-cli/src/commands/wallet/import.ts @@ -1,19 +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 { generateKeyFromPrivateKey, wordsToSpendingKey } from '@ironfish/rust-nodejs' -import { - ACCOUNT_SCHEMA_VERSION, - Bech32m, - JSONUtils, - LanguageUtils, - PromiseUtils, -} from '@ironfish/sdk' -import { RpcAccountImport } from '@ironfish/sdk/src/rpc/routes/wallet/types' +import { PromiseUtils } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' -import { CommandFlags } from '../../types' export class ImportCommand extends IronfishCommand { static description = `Import an account` @@ -48,48 +39,40 @@ export class ImportCommand extends IronfishCommand { const client = await this.sdk.connectRpc() - let account: RpcAccountImport + let account: string if (blob) { - account = await this.stringToRpcAccountImport(blob, flags) + account = blob } else if (flags.path) { - account = await this.importFile(flags.path, flags) + account = await this.importFile(flags.path) } else if (process.stdin.isTTY) { - account = await this.importTTY(flags) + account = await this.importTTY() } else if (!process.stdin.isTTY) { - account = await this.importPipe(flags) + account = await this.importPipe() } else { CliUx.ux.error(`Invalid import type`) } - if (!account.version) { - account.version = ACCOUNT_SCHEMA_VERSION - } - - if (!account.createdAt) { - account.createdAt = null - } - const accountsResponse = await client.wallet.getAccounts() const duplicateAccount = accountsResponse.content.accounts.find( - (accountName) => accountName === account.name, + (accountName) => accountName === flags.name, ) // Offer the user to use a different name if a duplicate is found - if (duplicateAccount && account.name) { + if (duplicateAccount && flags.name) { this.log() - this.log(`Found existing account with name '${account.name}'`) + this.log(`Found existing account with name '${flags.name}'`) const name = await CliUx.ux.prompt('Enter a different name for the account', { required: true, }) - if (name === account.name) { + if (name === flags.name) { this.error(`Entered the same name: '${name}'`) } - account.name = name + flags.name = name } const rescan = flags.rescan - const result = await client.wallet.importAccount({ account, rescan }) + const result = await client.wallet.importAccount({ account, rescan, name: flags.name }) const { name, isDefaultAccount } = result.content this.log(`Account ${name} imported.`) @@ -101,113 +84,13 @@ export class ImportCommand extends IronfishCommand { } } - static mnemonicWordsToKey(mnemonic: string): string | null { - let spendingKey: string | null = null - // There is no way to export size from MnemonicType in Rust (imperative) - if (mnemonic.trim().split(/\s+/).length !== 24) { - return null - } - for (const language of LanguageUtils.LANGUAGE_VALUES) { - try { - spendingKey = wordsToSpendingKey(mnemonic.trim(), language) - return spendingKey - } catch (e) { - continue - } - } - CliUx.ux.error( - `Detected mnemonic input, but the import failed. - Please verify the input text or use a different method to import wallet`, - ) - } - - static verifySpendingKey(spendingKey: string): string | null { - try { - return generateKeyFromPrivateKey(spendingKey)?.spendingKey ?? null - } catch (e) { - return null - } - } - - async stringToRpcAccountImport( - data: string, - flags: CommandFlags, - ): Promise { - // bech32 encoded json - const [decoded, _] = Bech32m.decode(data) - if (decoded) { - let data = JSONUtils.parse(decoded) - - if (data.spendingKey) { - data = { - ...data, - ...generateKeyFromPrivateKey(data.spendingKey), - } - } - - if (data.version === 1) { - data.createdAt = null - data.version = 2 - } - - if (flags.name) { - data.name = flags.name - } - - return data - } - - // mnemonic or explicit spending key - const spendingKey = - ImportCommand.mnemonicWordsToKey(data) || ImportCommand.verifySpendingKey(data) - - if (spendingKey) { - const name = - flags.name || - (await CliUx.ux.prompt('Enter a new account name', { - required: true, - })) - - const key = generateKeyFromPrivateKey(spendingKey) - return { name, version: ACCOUNT_SCHEMA_VERSION, createdAt: null, ...key } - } - - // raw json - try { - let json = JSONUtils.parse(data) - - if (json.spendingKey) { - json = { - ...json, - ...generateKeyFromPrivateKey(json.spendingKey), - } - } - - if (json.version === 1) { - json.createdAt = null - json.version = 2 - } - - if (flags.name) { - json.name = flags.name - } - - return json - } catch (e) { - CliUx.ux.error(`Import failed for the given input: ${data}`) - } - } - - async importFile( - path: string, - flags: CommandFlags, - ): Promise { + async importFile(path: string): Promise { const resolved = this.sdk.fileSystem.resolve(path) const data = await this.sdk.fileSystem.readFile(resolved) - return this.stringToRpcAccountImport(data.trim(), flags) + return data.trim() } - async importPipe(flags: CommandFlags): Promise { + async importPipe(): Promise { let data = '' const onData = (dataIn: string): void => { @@ -223,10 +106,10 @@ export class ImportCommand extends IronfishCommand { process.stdin.off('data', onData) - return this.stringToRpcAccountImport(data, flags) + return data } - async importTTY(flags: CommandFlags): Promise { + async importTTY(): Promise { const userInput = await CliUx.ux.prompt( 'Paste the output of wallet:export, or your spending key', { @@ -234,6 +117,6 @@ export class ImportCommand extends IronfishCommand { }, ) - return await this.stringToRpcAccountImport(userInput, flags) + return userInput.trim() } } From a160c11e2a0c9dba397de9c77c86ed8dc559e18b Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Wed, 5 Jul 2023 12:00:11 -0700 Subject: [PATCH 10/37] Verified assets: use the persisted cache when the node is not running When the command line client is running but not connected to any node, use the persisted cache (if present) to get and display verified assets information. --- ironfish/src/assets/assetsVerifier.test.ts | 48 +++++++++++----------- ironfish/src/assets/assetsVerifier.ts | 6 ++- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/ironfish/src/assets/assetsVerifier.test.ts b/ironfish/src/assets/assetsVerifier.test.ts index 72df16bfdc..2e17e367a9 100644 --- a/ironfish/src/assets/assetsVerifier.test.ts +++ b/ironfish/src/assets/assetsVerifier.test.ts @@ -145,30 +145,6 @@ describe('AssetsVerifier', () => { expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unknown' }) }) - it("returns 'unknown' after being stopped", async () => { - nock('https://test') - .get('/assets/verified') - .reply(200, { - data: [{ identifier: '0123' }], - }) - - const assetsVerifier = new AssetsVerifier({ - apiUrl: 'https://test/assets/verified', - }) - const refresh = jest.spyOn(assetsVerifier as any, 'refresh') - - assetsVerifier.start() - await waitForRefreshToFinish(refresh) - - expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) - expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) - - assetsVerifier.stop() - - expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unknown' }) - expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unknown' }) - }) - it("returns 'unknown' when the API is unreachable", async () => { nock('https://test').get('/assets/verified').reply(500) @@ -255,6 +231,30 @@ describe('AssetsVerifier', () => { expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) }) + + it('uses the in-memory cache after being stopped', async () => { + nock('https://test') + .get('/assets/verified') + .reply(200, { + data: [{ identifier: '0123' }], + }) + + const assetsVerifier = new AssetsVerifier({ + apiUrl: 'https://test/assets/verified', + }) + const refresh = jest.spyOn(assetsVerifier as any, 'refresh') + + assetsVerifier.start() + await waitForRefreshToFinish(refresh) + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) + + assetsVerifier.stop() + + expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' }) + expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) + }) }) describe('with persistent cache', () => { diff --git a/ironfish/src/assets/assetsVerifier.ts b/ironfish/src/assets/assetsVerifier.ts index 2c6fb379fd..dce5a6e9ff 100644 --- a/ironfish/src/assets/assetsVerifier.ts +++ b/ironfish/src/assets/assetsVerifier.ts @@ -95,9 +95,11 @@ export class AssetsVerifier { } verify(assetId: Buffer | string): AssetVerification { - if (!this.started || !this.verifiedAssets) { + if (!this.verifiedAssets) { return { status: 'unknown' } - } else if (this.verifiedAssets.isVerified(assetId)) { + } + + if (this.verifiedAssets.isVerified(assetId)) { return { status: 'verified' } } else { return { status: 'unverified' } From 87f6047fb5fbd5c6369722d5d0b16a1cb0aef7ac Mon Sep 17 00:00:00 2001 From: jowparks Date: Thu, 6 Jul 2023 16:28:44 -0700 Subject: [PATCH 11/37] fix: update exports for wallet (#4039) --- ironfish-cli/src/commands/wallet/export.ts | 8 ++++++-- .../src/rpc/routes/wallet/exportAccount.test.ts | 14 +++++++------- ironfish/src/rpc/routes/wallet/exportAccount.ts | 6 +++--- .../src/rpc/routes/wallet/importAccount.test.ts | 6 +++--- .../src/wallet/account/encoder/account.test.ts | 8 ++++---- ironfish/src/wallet/account/encoder/account.ts | 12 ++++++------ ironfish/src/wallet/account/encoder/encoder.ts | 2 +- ironfish/src/wallet/account/index.ts | 5 ----- ironfish/src/wallet/index.ts | 4 +++- 9 files changed, 33 insertions(+), 32 deletions(-) delete mode 100644 ironfish/src/wallet/account/index.ts diff --git a/ironfish-cli/src/commands/wallet/export.ts b/ironfish-cli/src/commands/wallet/export.ts index db06a2c0e7..4ae1a11bcf 100644 --- a/ironfish-cli/src/commands/wallet/export.ts +++ b/ironfish-cli/src/commands/wallet/export.ts @@ -1,7 +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/. */ -import { ErrorUtils, Format, LanguageUtils } from '@ironfish/sdk' +import { AccountFormat, ErrorUtils, LanguageUtils } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' import fs from 'fs' import jsonColorizer from 'json-colorizer' @@ -62,7 +62,11 @@ export class ExportCommand extends IronfishCommand { flags.mnemonic = true } - const format = flags.mnemonic ? Format.Mnemonic : flags.json ? Format.JSON : Format.Bech32 + const format = flags.mnemonic + ? AccountFormat.Mnemonic + : flags.json + ? AccountFormat.JSON + : AccountFormat.Bech32 const client = await this.sdk.connectRpc(local) const response = await client.wallet.exportAccount({ diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.test.ts b/ironfish/src/rpc/routes/wallet/exportAccount.test.ts index eeefa3574a..165c585db1 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.test.ts @@ -6,7 +6,7 @@ import { useAccountFixture } from '../../../testUtilities' import { createRouteTest } from '../../../testUtilities/routeTest' import { Account } from '../../../wallet' import { Bech32Encoder } from '../../../wallet/account/encoder/bech32' -import { Format } from '../../../wallet/account/encoder/encoder' +import { AccountFormat } from '../../../wallet/account/encoder/encoder' import { JsonEncoder } from '../../../wallet/account/encoder/json' import { MnemonicEncoder } from '../../../wallet/account/encoder/mnemonic' import { SpendingKeyEncoder } from '../../../wallet/account/encoder/spendingKey' @@ -69,7 +69,7 @@ describe('Route wallet/exportAccount', () => { const response = await routeTest.client .request('wallet/exportAccount', { account: account.name, - format: Format.JSON, + format: AccountFormat.JSON, }) .waitForEnd() @@ -83,7 +83,7 @@ describe('Route wallet/exportAccount', () => { const response = await routeTest.client .request('wallet/exportAccount', { account: account.name, - format: Format.Bech32, + format: AccountFormat.Bech32, }) .waitForEnd() @@ -95,7 +95,7 @@ describe('Route wallet/exportAccount', () => { const response = await routeTest.client .request('wallet/exportAccount', { account: account.name, - format: Format.SpendingKey, + format: AccountFormat.SpendingKey, }) .waitForEnd() @@ -108,7 +108,7 @@ describe('Route wallet/exportAccount', () => { routeTest.client .request('wallet/exportAccount', { account: account.name, - format: Format.SpendingKey, + format: AccountFormat.SpendingKey, viewOnly: true, }) .waitForEnd(), @@ -119,7 +119,7 @@ describe('Route wallet/exportAccount', () => { const response = await routeTest.client .request('wallet/exportAccount', { account: account.name, - format: Format.Mnemonic, + format: AccountFormat.Mnemonic, }) .waitForEnd() @@ -132,7 +132,7 @@ describe('Route wallet/exportAccount', () => { routeTest.client .request('wallet/exportAccount', { account: account.name, - format: Format.Mnemonic, + format: AccountFormat.Mnemonic, viewOnly: true, }) .waitForEnd(), diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.ts b/ironfish/src/rpc/routes/wallet/exportAccount.ts index 500e820a56..f00c726052 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { LanguageKey, LanguageUtils } from '../../../utils' import { encodeAccount } from '../../../wallet/account/encoder/account' -import { Format } from '../../../wallet/account/encoder/encoder' +import { AccountFormat } from '../../../wallet/account/encoder/encoder' import { ApiNamespace, router } from '../router' import { RpcAccountImport } from './types' import { getAccount } from './utils' @@ -12,7 +12,7 @@ import { getAccount } from './utils' export type ExportAccountRequest = { account?: string viewOnly?: boolean - format?: Format + format?: AccountFormat language?: LanguageKey } export type ExportAccountResponse = { @@ -23,7 +23,7 @@ export const ExportAccountRequestSchema: yup.ObjectSchema .object({ account: yup.string().trim(), viewOnly: yup.boolean().optional().default(false), - format: yup.string().oneOf(Object.values(Format)).optional(), + format: yup.string().oneOf(Object.values(AccountFormat)).optional(), language: yup.string().oneOf(LanguageUtils.LANGUAGE_KEYS).optional(), }) .defined() diff --git a/ironfish/src/rpc/routes/wallet/importAccount.test.ts b/ironfish/src/rpc/routes/wallet/importAccount.test.ts index 392ca2e1d5..9d4d203da3 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.test.ts @@ -6,7 +6,7 @@ import { generateKey, LanguageCode, spendingKeyToWords } from '@ironfish/rust-no import { createRouteTest } from '../../../testUtilities/routeTest' import { encodeAccount } from '../../../wallet/account/encoder/account' import { Bech32JsonEncoder } from '../../../wallet/account/encoder/bech32json' -import { Format } from '../../../wallet/account/encoder/encoder' +import { AccountFormat } from '../../../wallet/account/encoder/encoder' import { ImportResponse } from './importAccount' describe('Route wallet/importAccount', () => { @@ -84,7 +84,7 @@ describe('Route wallet/importAccount', () => { it('should import a string json encoded account', async () => { const name = 'json' - const jsonString = encodeAccount(createAccountImport(name), Format.JSON) + const jsonString = encodeAccount(createAccountImport(name), AccountFormat.JSON) const response = await routeTest.client .request('wallet/importAccount', { @@ -120,7 +120,7 @@ describe('Route wallet/importAccount', () => { it('should import a bech32 encoded account', async () => { const name = 'bech32' - const bech32 = encodeAccount(createAccountImport(name), Format.Bech32) + const bech32 = encodeAccount(createAccountImport(name), AccountFormat.Bech32) const response = await routeTest.client .request('wallet/importAccount', { diff --git a/ironfish/src/wallet/account/encoder/account.test.ts b/ironfish/src/wallet/account/encoder/account.test.ts index 29f5da81d3..8590053079 100644 --- a/ironfish/src/wallet/account/encoder/account.test.ts +++ b/ironfish/src/wallet/account/encoder/account.test.ts @@ -5,7 +5,7 @@ import { Assert } from '../../../assert' import { decodeAccount, encodeAccount } from './account' import { Bech32JsonEncoder } from './bech32json' -import { Format } from './encoder' +import { AccountFormat } from './encoder' describe('decodeAccount/encodeAccount', () => { describe('when decoding/encoding', () => { @@ -14,7 +14,7 @@ describe('decodeAccount/encodeAccount', () => { '{"version":2,"name":"ffff","spendingKey":"9e02be4c932ebc09c1eba0273a0ea41344615097222a5fb8a8787fba0db1a8fa","viewKey":"8d027bae046d73cf0be07e6024dd5719fb3bbdcac21cbb54b9850f6e4f89cd28fdb49856e5272870e497d65b177682f280938e379696dbdc689868eee5e52c1f","incomingViewKey":"348bd554fa8f1dc9686146ced3d483c48321880fc1a6cf323981bb2a41f99700","outgoingViewKey":"68543a20edaa435fb49155d1defb5141426c84d56728a8c5ae7692bc07875e3b","publicAddress":"471325ab136b883fe3dacff0f288153a9669dd4bae3d73b6578b33722a3bd22c","createdAt":{"hash":"000000000000007e3b8229e5fa28ecf70d7a34c973dd67b87160d4e55275a907","sequence":97654}}' const decoded = decodeAccount(jsonString) Assert.isNotNull(decoded) - const encoded = encodeAccount(decoded, Format.JSON) + const encoded = encodeAccount(decoded, AccountFormat.JSON) expect(encoded).toEqual(jsonString) }) @@ -24,11 +24,11 @@ describe('decodeAccount/encodeAccount', () => { const decoded = decodeAccount(jsonString) Assert.isNotNull(decoded) - const encodedJson = encodeAccount(decoded, Format.JSON) + const encodedJson = encodeAccount(decoded, AccountFormat.JSON) const decodedJson = decodeAccount(encodedJson, { name: 'new' }) expect(decodedJson.name).toEqual('new') - const encodedBech32 = encodeAccount(decoded, Format.Bech32) + const encodedBech32 = encodeAccount(decoded, AccountFormat.Bech32) const decodedBech32 = decodeAccount(encodedBech32, { name: 'new' }) expect(decodedBech32.name).toEqual('new') diff --git a/ironfish/src/wallet/account/encoder/account.ts b/ironfish/src/wallet/account/encoder/account.ts index cf24240f13..ceda88345e 100644 --- a/ironfish/src/wallet/account/encoder/account.ts +++ b/ironfish/src/wallet/account/encoder/account.ts @@ -5,7 +5,7 @@ import { Assert } from '../../../assert' import { AccountImport } from '../../walletdb/accountValue' import { Bech32Encoder } from './bech32' import { Bech32JsonEncoder } from './bech32json' -import { AccountDecodingOptions, AccountEncodingOptions, Format } from './encoder' +import { AccountDecodingOptions, AccountEncodingOptions, AccountFormat } from './encoder' import { JsonEncoder } from './json' import { MnemonicEncoder } from './mnemonic' import { SpendingKeyEncoder } from './spendingKey' @@ -20,17 +20,17 @@ const ENCODER_VERSIONS = [ export function encodeAccount( value: AccountImport, - format: Format, + format: AccountFormat, options: AccountEncodingOptions = {}, ): string { switch (format) { - case Format.JSON: + case AccountFormat.JSON: return new JsonEncoder().encode(value) - case Format.Bech32: + case AccountFormat.Bech32: return new Bech32Encoder().encode(value) - case Format.SpendingKey: + case AccountFormat.SpendingKey: return new SpendingKeyEncoder().encode(value) - case Format.Mnemonic: + case AccountFormat.Mnemonic: return new MnemonicEncoder().encode(value, options) default: return Assert.isUnreachable(format) diff --git a/ironfish/src/wallet/account/encoder/encoder.ts b/ironfish/src/wallet/account/encoder/encoder.ts index 0b1eec5fe0..b7b1b47844 100644 --- a/ironfish/src/wallet/account/encoder/encoder.ts +++ b/ironfish/src/wallet/account/encoder/encoder.ts @@ -4,7 +4,7 @@ import { LanguageKey } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' -export enum Format { +export enum AccountFormat { JSON = 'JSON', Bech32 = 'Bech32', SpendingKey = 'SpendingKey', diff --git a/ironfish/src/wallet/account/index.ts b/ironfish/src/wallet/account/index.ts deleted file mode 100644 index 6fff040fa7..0000000000 --- a/ironfish/src/wallet/account/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* 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 * from './account' -export { Format } from './encoder/encoder' diff --git a/ironfish/src/wallet/index.ts b/ironfish/src/wallet/index.ts index d60f04290d..eaf289fc47 100644 --- a/ironfish/src/wallet/index.ts +++ b/ironfish/src/wallet/index.ts @@ -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/. */ -export * from './account' +export * from './account/account' export * from './wallet' +export * from './account/encoder/encoder' +export * from './account/encoder/account' export { AccountValue } from './walletdb/accountValue' export * from './validator' export * from './walletdb/walletdb' From 76c9063dc96bc7fa477bb6396425981fcbb82e65 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 6 Jul 2023 16:34:04 -0700 Subject: [PATCH 12/37] converts import-export-test to rpc unit tests (#4037) implements unit test to read old account file formats and submit to the importAccount rpc endpoint fixes import of some old account formats that used a date string in the createdAt field --- .../scripts/import-export-test/README | 1 - .../__importTestCases__}/0p1p65_bech32.txt | 0 .../__importTestCases__}/0p1p65_json.txt | 0 .../__importTestCases__}/0p1p65_mnemonic.txt | 0 .../__importTestCases__}/0p1p66_bech32.txt | 0 .../__importTestCases__}/0p1p66_json.txt | 0 .../__importTestCases__}/0p1p68_bech32.txt | 0 .../__importTestCases__}/0p1p68_json.txt | 0 .../__importTestCases__}/0p1p71_bech32.txt | 0 .../__importTestCases__}/0p1p71_json.txt | 0 .../__importTestCases__}/0p1p75_bech32.txt | 0 .../__importTestCases__}/0p1p75_json.txt | 0 .../rpc/routes/wallet/importAccount.test.ts | 23 +++++++++++++++++++ ironfish/src/wallet/account/encoder/json.ts | 5 ++++ 14 files changed, 28 insertions(+), 1 deletion(-) delete mode 100644 ironfish-cli/scripts/import-export-test/README rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p65_bech32.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p65_json.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p65_mnemonic.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p66_bech32.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p66_json.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p68_bech32.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p68_json.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p71_bech32.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p71_json.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p75_bech32.txt (100%) rename {ironfish-cli/scripts/import-export-test => ironfish/src/rpc/routes/wallet/__importTestCases__}/0p1p75_json.txt (100%) diff --git a/ironfish-cli/scripts/import-export-test/README b/ironfish-cli/scripts/import-export-test/README deleted file mode 100644 index a06009aadb..0000000000 --- a/ironfish-cli/scripts/import-export-test/README +++ /dev/null @@ -1 +0,0 @@ -When ever the exported wallet format changes, the new wallet format should be included here and it will get automatically tested. diff --git a/ironfish-cli/scripts/import-export-test/0p1p65_bech32.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p65_bech32.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p65_bech32.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p65_bech32.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p65_json.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p65_json.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p65_json.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p65_json.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p65_mnemonic.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p65_mnemonic.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p65_mnemonic.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p65_mnemonic.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p66_bech32.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p66_bech32.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p66_bech32.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p66_bech32.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p66_json.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p66_json.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p66_json.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p66_json.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p68_bech32.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p68_bech32.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p68_bech32.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p68_bech32.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p68_json.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p68_json.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p68_json.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p68_json.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p71_bech32.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p71_bech32.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p71_bech32.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p71_bech32.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p71_json.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p71_json.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p71_json.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p71_json.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p75_bech32.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p75_bech32.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p75_bech32.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p75_bech32.txt diff --git a/ironfish-cli/scripts/import-export-test/0p1p75_json.txt b/ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p75_json.txt similarity index 100% rename from ironfish-cli/scripts/import-export-test/0p1p75_json.txt rename to ironfish/src/rpc/routes/wallet/__importTestCases__/0p1p75_json.txt diff --git a/ironfish/src/rpc/routes/wallet/importAccount.test.ts b/ironfish/src/rpc/routes/wallet/importAccount.test.ts index 9d4d203da3..3feb249ba8 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.test.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.test.ts @@ -3,6 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { generateKey, LanguageCode, spendingKeyToWords } from '@ironfish/rust-nodejs' +import fs from 'fs' +import path from 'path' import { createRouteTest } from '../../../testUtilities/routeTest' import { encodeAccount } from '../../../wallet/account/encoder/account' import { Bech32JsonEncoder } from '../../../wallet/account/encoder/bech32json' @@ -173,5 +175,26 @@ describe('Route wallet/importAccount', () => { isDefaultAccount: false, // This is false because the default account is already imported in a previous test }) }) + + it('should support importing old account export formats', async () => { + const testCaseDir = path.join(__dirname, '__importTestCases__') + const importTestCaseFiles = fs.readdirSync(testCaseDir) + + for (const testCaseFile of importTestCaseFiles) { + const testCase = await routeTest.sdk.fileSystem.readFile( + path.join(testCaseDir, testCaseFile), + ) + + const response = await routeTest.client + .request('wallet/importAccount', { + account: testCase, + name: testCaseFile, + }) + .waitForEnd() + + expect(response.status).toBe(200) + expect(response.content.name).not.toBeNull() + } + }) }) }) diff --git a/ironfish/src/wallet/account/encoder/json.ts b/ironfish/src/wallet/account/encoder/json.ts index fcfdacd284..7082366200 100644 --- a/ironfish/src/wallet/account/encoder/json.ts +++ b/ironfish/src/wallet/account/encoder/json.ts @@ -20,6 +20,11 @@ export class JsonEncoder implements AccountEncoder { decode(value: string, options?: AccountDecodingOptions): AccountImport { const account = JSON.parse(value) as RpcAccountImport + + if (account.createdAt && !account.createdAt.hash) { + account.createdAt = null + } + const updatedAccount = { ...account, name: options?.name ? options.name : account.name, From 09e210022a8ea058c2af0b60b9921223d373afb0 Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Thu, 6 Jul 2023 16:11:10 -0700 Subject: [PATCH 13/37] Verified assets: place the cache in the `temp` directory Reuse the already-existing `temp` directory instead of creating a new `cache` directory. --- ironfish/src/fileStores/verifiedAssets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/fileStores/verifiedAssets.ts b/ironfish/src/fileStores/verifiedAssets.ts index ee6cb1bf74..82ecf78d60 100644 --- a/ironfish/src/fileStores/verifiedAssets.ts +++ b/ironfish/src/fileStores/verifiedAssets.ts @@ -17,7 +17,7 @@ export const VerifiedAssetsCacheOptionsDefaults: VerifiedAssetsCacheOptions = { assetIds: [], } -export const VERIFIED_ASSETS_CACHE_FILE_NAME = path.join('cache', 'verified-assets.json') +export const VERIFIED_ASSETS_CACHE_FILE_NAME = path.join('temp', 'verified-assets.json') export class VerifiedAssetsCacheStore extends KeyStore { logger: Logger From 73818bcd0834c3b68ea2cb2b58b62cecf856623a Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Thu, 6 Jul 2023 05:14:00 -0700 Subject: [PATCH 14/37] Verified assets: add support for file:/// URIs as the source of verified assets --- .../src/assets/assetsVerificationApi.test.ts | 16 ++++++++++ ironfish/src/assets/assetsVerificationApi.ts | 32 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/ironfish/src/assets/assetsVerificationApi.test.ts b/ironfish/src/assets/assetsVerificationApi.test.ts index 0977fa04c7..03dd58d111 100644 --- a/ironfish/src/assets/assetsVerificationApi.test.ts +++ b/ironfish/src/assets/assetsVerificationApi.test.ts @@ -1,6 +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/. */ +import fs from 'fs' import nock from 'nock' import { AssetsVerificationApi } from './assetsVerificationApi' @@ -146,5 +147,20 @@ describe('Assets Verification API Client', () => { }) await expect(api.getVerifiedAssets()).rejects.toThrow('timeout of 1000ms exceeded') }) + + it('supports file:// URIs', async () => { + const contents = JSON.stringify({ + data: [{ identifier: '0123' }, { identifier: 'abcd' }], + }) + const readFileSpy = jest.spyOn(fs.promises, 'readFile').mockResolvedValue(contents) + + const api = new AssetsVerificationApi({ + url: 'file:///some/where', + }) + const verifiedAssets = await api.getVerifiedAssets() + + expect(verifiedAssets['assetIds']).toStrictEqual(new Set(['0123', 'abcd'])) + expect(readFileSpy).toHaveBeenCalledWith('/some/where', { encoding: 'utf8' }) + }) }) }) diff --git a/ironfish/src/assets/assetsVerificationApi.ts b/ironfish/src/assets/assetsVerificationApi.ts index 8e7f7588a8..cc11942521 100644 --- a/ironfish/src/assets/assetsVerificationApi.ts +++ b/ironfish/src/assets/assetsVerificationApi.ts @@ -1,7 +1,9 @@ /* 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 axios, { AxiosError } from 'axios' +import axios, { AxiosAdapter, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios' +import { readFile } from 'node:fs/promises' +import url, { URL } from 'url' type GetVerifiedAssetsResponse = { data: Array<{ identifier: string }> @@ -56,12 +58,14 @@ export type ExportedVerifiedAssets = { export class AssetsVerificationApi { private readonly timeout: number + private readonly adapter?: AxiosAdapter readonly url: string constructor(options?: { url?: string; timeout?: number }) { this.url = options?.url || 'https://api.ironfish.network/assets?verified=true' this.timeout = options?.timeout ?? 30 * 1000 // 30 seconds + this.adapter = isFileUrl(this.url) ? axiosFileAdapter : axios.defaults.adapter } async getVerifiedAssets(): Promise { @@ -83,6 +87,7 @@ export class AssetsVerificationApi { .get(this.url, { headers: headers, timeout: this.timeout, + adapter: this.adapter, }) .then( (response: { @@ -105,3 +110,28 @@ export class AssetsVerificationApi { }) } } + +const isFileUrl = (url: string): boolean => { + const parsedUrl = new URL(url) + return parsedUrl.protocol === 'file:' +} + +const axiosFileAdapter = ( + config: AxiosRequestConfig, +): Promise> => { + if (!config.url) { + return Promise.reject(new Error('url is undefined')) + } + + const path = url.fileURLToPath(config.url) + + return readFile(path, { encoding: 'utf8' }) + .then(JSON.parse) + .then((data: GetVerifiedAssetsResponse) => ({ + data, + status: 0, + statusText: '', + headers: {}, + config: config, + })) +} From 10e3f98e34df4a2a0afa01f78ccafbd71c104c56 Mon Sep 17 00:00:00 2001 From: jowparks Date: Fri, 7 Jul 2023 14:49:03 -0700 Subject: [PATCH 15/37] fix: roll up errors from decodeAccount function (#4048) --- ironfish/src/wallet/account/encoder/account.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ironfish/src/wallet/account/encoder/account.ts b/ironfish/src/wallet/account/encoder/account.ts index ceda88345e..40f0cdd95f 100644 --- a/ironfish/src/wallet/account/encoder/account.ts +++ b/ironfish/src/wallet/account/encoder/account.ts @@ -42,15 +42,18 @@ export function decodeAccount( options: AccountDecodingOptions = {}, ): AccountImport { let decoded = null + const errors: { name: string; err: Error }[] = [] for (const encoder of ENCODER_VERSIONS) { try { decoded = new encoder().decode(value, options) } catch (e) { + errors.push({ name: encoder.name, err: e as Error }) continue } if (decoded) { return decoded } } - throw new Error('Account could not be decoded') + const errorString = errors.map((error) => `${error.name}: ${error.err.message}`).join('\n') + throw new Error(`Account could not be decoded, decoder errors:\n${errorString} `) } From d4b9a54a89493c06c71b49fc7c1c202f61cffe7c Mon Sep 17 00:00:00 2001 From: jowparks Date: Mon, 10 Jul 2023 09:32:36 -0700 Subject: [PATCH 16/37] fix: granular error handling account decoding (#4056) --- .../wallet/account/encoder/account.test.ts | 14 ++++ .../src/wallet/account/encoder/account.ts | 15 +++- ironfish/src/wallet/account/encoder/bech32.ts | 69 ++++++++++++------- .../src/wallet/account/encoder/bech32json.ts | 6 +- .../src/wallet/account/encoder/encoder.ts | 4 ++ ironfish/src/wallet/account/encoder/json.ts | 39 ++++++----- .../src/wallet/account/encoder/mnemonic.ts | 16 +++-- .../src/wallet/account/encoder/spendingKey.ts | 14 ++-- 8 files changed, 118 insertions(+), 59 deletions(-) diff --git a/ironfish/src/wallet/account/encoder/account.test.ts b/ironfish/src/wallet/account/encoder/account.test.ts index 8590053079..4d99f623cb 100644 --- a/ironfish/src/wallet/account/encoder/account.test.ts +++ b/ironfish/src/wallet/account/encoder/account.test.ts @@ -41,5 +41,19 @@ describe('decodeAccount/encodeAccount', () => { const invalidJson = '{}' expect(() => decodeAccount(invalidJson)).toThrow() }) + it('throws when name is not passed, but mnemonic is valid', () => { + const mnemonic = + 'own bicycle nasty chaos type agent amateur inject cheese spare poverty charge ecology portion frame earn garden shed bulk youth patch sugar physical family' + expect(() => decodeAccount(mnemonic)).toThrow( + 'Name option is required for mnemonic key encoder', + ) + }) + + it('throws when name is not passed, but spending key is valid', () => { + const spendingKey = '9e02be4c932ebc09c1eba0273a0ea41344615097222a5fb8a8787fba0db1a8fa' + expect(() => decodeAccount(spendingKey)).toThrow( + 'Name option is required for spending key encoder', + ) + }) }) }) diff --git a/ironfish/src/wallet/account/encoder/account.ts b/ironfish/src/wallet/account/encoder/account.ts index 40f0cdd95f..066b4d8ce8 100644 --- a/ironfish/src/wallet/account/encoder/account.ts +++ b/ironfish/src/wallet/account/encoder/account.ts @@ -5,7 +5,12 @@ import { Assert } from '../../../assert' import { AccountImport } from '../../walletdb/accountValue' import { Bech32Encoder } from './bech32' import { Bech32JsonEncoder } from './bech32json' -import { AccountDecodingOptions, AccountEncodingOptions, AccountFormat } from './encoder' +import { + AccountDecodingOptions, + AccountEncodingOptions, + AccountFormat, + DecodeFailed, +} from './encoder' import { JsonEncoder } from './json' import { MnemonicEncoder } from './mnemonic' import { SpendingKeyEncoder } from './spendingKey' @@ -47,8 +52,12 @@ export function decodeAccount( try { decoded = new encoder().decode(value, options) } catch (e) { - errors.push({ name: encoder.name, err: e as Error }) - continue + if (e instanceof DecodeFailed) { + errors.push({ name: encoder.name, err: e as Error }) + continue + } else { + throw e + } } if (decoded) { return decoded diff --git a/ironfish/src/wallet/account/encoder/bech32.ts b/ironfish/src/wallet/account/encoder/bech32.ts index 5769c052ea..b483ea2a94 100644 --- a/ironfish/src/wallet/account/encoder/bech32.ts +++ b/ironfish/src/wallet/account/encoder/bech32.ts @@ -2,11 +2,11 @@ * 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 { PUBLIC_ADDRESS_LENGTH } from '@ironfish/rust-nodejs' -import bufio from 'bufio' +import bufio, { EncodingError } from 'bufio' import { Bech32m } from '../../../utils' import { AccountImport, KEY_LENGTH, VIEW_KEY_LENGTH } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' -import { AccountDecodingOptions, AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder, DecodeFailed, DecodeInvalid } from './encoder' export const BECH32_ACCOUNT_PREFIX = 'ifaccount' export class Bech32Encoder implements AccountEncoder { @@ -37,39 +37,56 @@ export class Bech32Encoder implements AccountEncoder { } decode(value: string, options?: AccountDecodingOptions): AccountImport { - const [hexEncoding, _] = Bech32m.decode(value) + const [hexEncoding, err] = Bech32m.decode(value) - if (hexEncoding === null) { - throw new Error(`Could not decode account ${value} using bech32`) + if (!hexEncoding) { + throw new DecodeFailed( + `Could not decode account ${value} using bech32: ${err?.message || ''}`, + ) } - const buffer = Buffer.from(hexEncoding, 'hex') + let name: string + let viewKey: string + let incomingViewKey: string + let outgoingViewKey: string + let publicAddress: string + let spendingKey: string | null + let createdAt = null - const reader = bufio.read(buffer, true) + try { + const buffer = Buffer.from(hexEncoding, 'hex') - const version = reader.readU16() + const reader = bufio.read(buffer, true) - if (version !== this.VERSION) { - throw new Error( - `Encoded account version ${version} does not match encoder version ${this.VERSION}`, - ) - } + const version = reader.readU16() - const name = reader.readVarString('utf8') - const viewKey = reader.readBytes(VIEW_KEY_LENGTH).toString('hex') - const incomingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') - const outgoingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') - const publicAddress = reader.readBytes(PUBLIC_ADDRESS_LENGTH).toString('hex') + if (version !== this.VERSION) { + throw new DecodeInvalid( + `Encoded account version ${version} does not match encoder version ${this.VERSION}`, + ) + } - const hasSpendingKey = reader.readU8() === 1 - const spendingKey = hasSpendingKey ? reader.readBytes(KEY_LENGTH).toString('hex') : null + name = reader.readVarString('utf8') + viewKey = reader.readBytes(VIEW_KEY_LENGTH).toString('hex') + incomingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + outgoingViewKey = reader.readBytes(KEY_LENGTH).toString('hex') + publicAddress = reader.readBytes(PUBLIC_ADDRESS_LENGTH).toString('hex') - const hasCreatedAt = reader.readU8() === 1 - let createdAt = null - if (hasCreatedAt) { - const hash = reader.readBytes(32) - const sequence = reader.readU32() - createdAt = { hash, sequence } + const hasSpendingKey = reader.readU8() === 1 + spendingKey = hasSpendingKey ? reader.readBytes(KEY_LENGTH).toString('hex') : null + + const hasCreatedAt = reader.readU8() === 1 + + if (hasCreatedAt) { + const hash = reader.readBytes(32) + const sequence = reader.readU32() + createdAt = { hash, sequence } + } + } catch (e) { + if (e instanceof EncodingError) { + throw new DecodeFailed(`Bufio decoding failed while using bech32 encoder: ${e.message}`) + } + throw e } return { diff --git a/ironfish/src/wallet/account/encoder/bech32json.ts b/ironfish/src/wallet/account/encoder/bech32json.ts index bd32b045a4..66ab4d6be2 100644 --- a/ironfish/src/wallet/account/encoder/bech32json.ts +++ b/ironfish/src/wallet/account/encoder/bech32json.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Bech32m } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' -import { AccountDecodingOptions, AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder, DecodeFailed } from './encoder' import { JsonEncoder } from './json' export class Bech32JsonEncoder implements AccountEncoder { /** @@ -14,9 +14,9 @@ export class Bech32JsonEncoder implements AccountEncoder { } decode(value: string, options?: AccountDecodingOptions): AccountImport { - const [decoded, _] = Bech32m.decode(value) + const [decoded, err] = Bech32m.decode(value) if (!decoded) { - throw new Error('Invalid bech32 JSON encoding') + throw new DecodeFailed(`Invalid bech32 JSON encoding: ${err?.message || ''}`) } const accountImport = new JsonEncoder().decode(decoded) return { diff --git a/ironfish/src/wallet/account/encoder/encoder.ts b/ironfish/src/wallet/account/encoder/encoder.ts index b7b1b47844..90e64f4de2 100644 --- a/ironfish/src/wallet/account/encoder/encoder.ts +++ b/ironfish/src/wallet/account/encoder/encoder.ts @@ -4,6 +4,10 @@ import { LanguageKey } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' +export class DecodeInvalid extends Error {} + +export class DecodeFailed extends Error {} + export enum AccountFormat { JSON = 'JSON', Bech32 = 'Bech32', diff --git a/ironfish/src/wallet/account/encoder/json.ts b/ironfish/src/wallet/account/encoder/json.ts index 7082366200..ab9aed77ca 100644 --- a/ironfish/src/wallet/account/encoder/json.ts +++ b/ironfish/src/wallet/account/encoder/json.ts @@ -4,7 +4,7 @@ import { RpcAccountImport } from '../../../rpc/routes/wallet/types' import { validateAccount } from '../../validator' import { AccountImport } from '../../walletdb/accountValue' -import { AccountDecodingOptions, AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder, DecodeFailed } from './encoder' export class JsonEncoder implements AccountEncoder { encode(value: AccountImport): string { @@ -19,23 +19,26 @@ export class JsonEncoder implements AccountEncoder { } decode(value: string, options?: AccountDecodingOptions): AccountImport { - const account = JSON.parse(value) as RpcAccountImport - - if (account.createdAt && !account.createdAt.hash) { - account.createdAt = null + let account: RpcAccountImport + try { + account = JSON.parse(value) as RpcAccountImport + if (account.createdAt && !account.createdAt.hash) { + account.createdAt = null + } + const accountImport = { + ...account, + name: options?.name ? options.name : account.name, + createdAt: account.createdAt + ? { + hash: Buffer.from(account.createdAt.hash, 'hex'), + sequence: account.createdAt.sequence, + } + : null, + } + validateAccount(accountImport) + return accountImport + } catch (e) { + throw new DecodeFailed(`Invalid JSON: ${(e as Error).message}`) } - - const updatedAccount = { - ...account, - name: options?.name ? options.name : account.name, - createdAt: account.createdAt - ? { - hash: Buffer.from(account.createdAt.hash, 'hex'), - sequence: account.createdAt.sequence, - } - : null, - } as AccountImport - validateAccount(updatedAccount) - return updatedAccount } } diff --git a/ironfish/src/wallet/account/encoder/mnemonic.ts b/ironfish/src/wallet/account/encoder/mnemonic.ts index ee9dc4ad1f..1b3f84d316 100644 --- a/ironfish/src/wallet/account/encoder/mnemonic.ts +++ b/ironfish/src/wallet/account/encoder/mnemonic.ts @@ -10,7 +10,13 @@ import { import { LanguageUtils } from '../../../utils' import { AccountImport } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' -import { AccountDecodingOptions, AccountEncoder, AccountEncodingOptions } from './encoder' +import { + AccountDecodingOptions, + AccountEncoder, + AccountEncodingOptions, + DecodeFailed, + DecodeInvalid, +} from './encoder' export class MnemonicEncoder implements AccountEncoder { encode(value: AccountImport, options: AccountEncodingOptions): string { @@ -27,9 +33,6 @@ export class MnemonicEncoder implements AccountEncoder { } decode(value: string, options: AccountDecodingOptions): AccountImport { - if (!options.name) { - throw new Error('Name option is required for mnemonic key encoder') - } let spendingKey = '' let language = null for (const code of Object.values(LanguageUtils.LANGUAGES)) { @@ -41,7 +44,10 @@ export class MnemonicEncoder implements AccountEncoder { language = LanguageUtils.languageCodeToKey(code) } if (language === null) { - throw new Error('Invalid mnemonic') + throw new DecodeFailed('Invalid mnemonic') + } + if (!options.name) { + throw new DecodeInvalid('Name option is required for mnemonic key encoder') } const key = generateKeyFromPrivateKey(spendingKey) return { diff --git a/ironfish/src/wallet/account/encoder/spendingKey.ts b/ironfish/src/wallet/account/encoder/spendingKey.ts index 4323ee22b3..2a2777d2ea 100644 --- a/ironfish/src/wallet/account/encoder/spendingKey.ts +++ b/ironfish/src/wallet/account/encoder/spendingKey.ts @@ -1,10 +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 { generateKeyFromPrivateKey } from '@ironfish/rust-nodejs' +import { generateKeyFromPrivateKey, Key } from '@ironfish/rust-nodejs' import { AccountImport } from '../../walletdb/accountValue' import { ACCOUNT_SCHEMA_VERSION } from '../account' -import { AccountDecodingOptions, AccountEncoder } from './encoder' +import { AccountDecodingOptions, AccountEncoder, DecodeFailed, DecodeInvalid } from './encoder' export class SpendingKeyEncoder implements AccountEncoder { encode(value: AccountImport): string { @@ -15,10 +15,16 @@ export class SpendingKeyEncoder implements AccountEncoder { } decode(spendingKey: string, options: AccountDecodingOptions): AccountImport { + let key: Key + try { + key = generateKeyFromPrivateKey(spendingKey) + } catch (e) { + throw new DecodeFailed(`Invalid spending key: ${(e as Error).message}`) + } + if (!options.name) { - throw new Error('Name option is required for spending key encoder') + throw new DecodeInvalid('Name option is required for spending key encoder') } - const key = generateKeyFromPrivateKey(spendingKey) return { name: options.name, spendingKey: spendingKey, From a70e568a00391e1820c6ab68e952041f01106130 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Mon, 10 Jul 2023 14:57:41 -0700 Subject: [PATCH 17/37] Remove temporary variable rescan This is only used in one place, so we can just pass this in. --- ironfish-cli/src/commands/wallet/import.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ironfish-cli/src/commands/wallet/import.ts b/ironfish-cli/src/commands/wallet/import.ts index d725f27ebe..dac7e0b178 100644 --- a/ironfish-cli/src/commands/wallet/import.ts +++ b/ironfish-cli/src/commands/wallet/import.ts @@ -71,8 +71,11 @@ export class ImportCommand extends IronfishCommand { flags.name = name } - const rescan = flags.rescan - const result = await client.wallet.importAccount({ account, rescan, name: flags.name }) + const result = await client.wallet.importAccount({ + account, + rescan: flags.rescan, + name: flags.name, + }) const { name, isDefaultAccount } = result.content this.log(`Account ${name} imported.`) From a1892b3e778b7cf8870d48bfe703332b4db502e3 Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Fri, 7 Jul 2023 07:15:42 -0700 Subject: [PATCH 18/37] Verified assets: use the new `/verified/assets` API endpoint --- .../src/assets/assetsVerificationApi.test.ts | 16 ++++++------ ironfish/src/assets/assetsVerificationApi.ts | 6 ++--- ironfish/src/assets/assetsVerifier.test.ts | 26 +++++++++---------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/ironfish/src/assets/assetsVerificationApi.test.ts b/ironfish/src/assets/assetsVerificationApi.test.ts index 03dd58d111..3fb517b4bf 100644 --- a/ironfish/src/assets/assetsVerificationApi.test.ts +++ b/ironfish/src/assets/assetsVerificationApi.test.ts @@ -20,7 +20,7 @@ describe('Assets Verification API Client', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }, { identifier: 'abcd' }], + assets: [{ identifier: '0123' }, { identifier: 'abcd' }], }) const api = new AssetsVerificationApi({ @@ -39,7 +39,7 @@ describe('Assets Verification API Client', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [ + assets: [ { identifier: '0123', extra: 'should be ignored' }, { identifier: 'abcd', extra: 'should be ignored' }, ], @@ -58,11 +58,11 @@ describe('Assets Verification API Client', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }, { identifier: 'abcd' }], + assets: [{ identifier: '0123' }, { identifier: 'abcd' }], }) .get('/assets/verified') .reply(200, { - data: [{ identifier: '4567' }, { identifier: '0123' }], + assets: [{ identifier: '4567' }, { identifier: '0123' }], }) const api = new AssetsVerificationApi({ @@ -84,7 +84,7 @@ describe('Assets Verification API Client', () => { .reply( 200, { - data: [{ identifier: '0123' }, { identifier: 'abcd' }], + assets: [{ identifier: '0123' }, { identifier: 'abcd' }], }, { 'last-modified': lastModified, @@ -123,7 +123,7 @@ describe('Assets Verification API Client', () => { .get('/assets/verified') .delayConnection(2000) .reply(200, { - data: [{ identifier: '0123' }, { identifier: 'abcd' }], + assets: [{ identifier: '0123' }, { identifier: 'abcd' }], }) const api = new AssetsVerificationApi({ @@ -138,7 +138,7 @@ describe('Assets Verification API Client', () => { .get('/assets/verified') .delay(2000) .reply(200, { - data: [{ identifier: '0123' }, { identifier: 'abcd' }], + assets: [{ identifier: '0123' }, { identifier: 'abcd' }], }) const api = new AssetsVerificationApi({ @@ -150,7 +150,7 @@ describe('Assets Verification API Client', () => { it('supports file:// URIs', async () => { const contents = JSON.stringify({ - data: [{ identifier: '0123' }, { identifier: 'abcd' }], + assets: [{ identifier: '0123' }, { identifier: 'abcd' }], }) const readFileSpy = jest.spyOn(fs.promises, 'readFile').mockResolvedValue(contents) diff --git a/ironfish/src/assets/assetsVerificationApi.ts b/ironfish/src/assets/assetsVerificationApi.ts index cc11942521..f71049e2cc 100644 --- a/ironfish/src/assets/assetsVerificationApi.ts +++ b/ironfish/src/assets/assetsVerificationApi.ts @@ -6,7 +6,7 @@ import { readFile } from 'node:fs/promises' import url, { URL } from 'url' type GetVerifiedAssetsResponse = { - data: Array<{ identifier: string }> + assets: Array<{ identifier: string }> } type GetVerifiedAssetsRequestHeaders = { @@ -63,7 +63,7 @@ export class AssetsVerificationApi { readonly url: string constructor(options?: { url?: string; timeout?: number }) { - this.url = options?.url || 'https://api.ironfish.network/assets?verified=true' + this.url = options?.url || 'https://api.ironfish.network/assets/verified' this.timeout = options?.timeout ?? 30 * 1000 // 30 seconds this.adapter = isFileUrl(this.url) ? axiosFileAdapter : axios.defaults.adapter } @@ -95,7 +95,7 @@ export class AssetsVerificationApi { headers: GetVerifiedAssetsResponseHeaders }) => { verifiedAssets['assetIds'].clear() - response.data.data.forEach(({ identifier }) => { + response.data.assets.forEach(({ identifier }) => { return verifiedAssets['assetIds'].add(identifier) }) verifiedAssets['lastModified'] = response.headers['last-modified'] diff --git a/ironfish/src/assets/assetsVerifier.test.ts b/ironfish/src/assets/assetsVerifier.test.ts index 2e17e367a9..6013b29223 100644 --- a/ironfish/src/assets/assetsVerifier.test.ts +++ b/ironfish/src/assets/assetsVerifier.test.ts @@ -38,15 +38,15 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) .get('/assets/verified') .reply(200, { - data: [{ identifier: '4567' }], + assets: [{ identifier: '4567' }], }) .get('/assets/verified') .reply(200, { - data: [{ identifier: '89ab' }], + assets: [{ identifier: '89ab' }], }) const assetsVerifier = new AssetsVerifier({ @@ -87,7 +87,7 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) const assetsVerifier = new AssetsVerifier({ @@ -110,7 +110,7 @@ describe('AssetsVerifier', () => { .reply( 200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }, { 'last-modified': 'some-date' }, ) @@ -168,7 +168,7 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) const assetsVerifier = new AssetsVerifier({ @@ -186,7 +186,7 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) const assetsVerifier = new AssetsVerifier({ @@ -204,7 +204,7 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) .get('/assets/verified') .reply(500) @@ -236,7 +236,7 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) const assetsVerifier = new AssetsVerifier({ @@ -270,7 +270,7 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '4567' }], + assets: [{ identifier: '4567' }], }) const assetsVerifier = new AssetsVerifier({ @@ -299,7 +299,7 @@ describe('AssetsVerifier', () => { nock('https://bar.test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '4567' }], + assets: [{ identifier: '4567' }], }) const assetsVerifier = new AssetsVerifier({ @@ -327,13 +327,13 @@ describe('AssetsVerifier', () => { nock('https://test') .get('/assets/verified') .reply(200, { - data: [{ identifier: '0123' }], + assets: [{ identifier: '0123' }], }) .get('/assets/verified') .reply( 200, { - data: [{ identifier: '4567' }], + assets: [{ identifier: '4567' }], }, { 'last-modified': 'some-date' }, ) From 7ddb0b1bce2f36b4f93917f734acd0c2b8dadc8c Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Wed, 12 Jul 2023 08:15:49 -0700 Subject: [PATCH 19/37] chore: update libuv-monitor 0.0.5 for node-app compatibility changes (#4059) --- ironfish/package.json | 2 +- yarn.lock | 54 +++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ironfish/package.json b/ironfish/package.json index 92d1638f25..81904434cb 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -39,7 +39,7 @@ "level-errors": "2.0.1", "leveldown": "5.6.0", "levelup": "4.4.0", - "libuv-monitor": "0.0.3", + "libuv-monitor": "0.0.5", "lodash": "4.17.21", "node-datachannel": "0.4.0", "node-forge": "1.3.1", diff --git a/yarn.lock b/yarn.lock index 7add9cbf92..4289964f6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2611,25 +2611,25 @@ semver "^7.3.5" tar "^6.1.11" -"@mgeist/libuv-monitor-darwin-arm64@0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-darwin-arm64/-/libuv-monitor-darwin-arm64-0.0.3.tgz#3fa9ec01a9a7e437b5bf894e9f03b59fe26411b6" - integrity sha512-jaHid8UEDNyASzBeY5qrbC5SJfFYr9z9wNuGhJbkVXQT//JN9fxe2g/YHJjEGR/u5P4nDy6EUeqXfnIdSiNuTQ== - -"@mgeist/libuv-monitor-darwin-x64@0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-darwin-x64/-/libuv-monitor-darwin-x64-0.0.3.tgz#7887419f3e8d409f0426772ea14d4f2399339aeb" - integrity sha512-k+CAZ3xUXu3JX0P25N6yhcvfl6h5XQv2D/RDyCJ0I+OSoL5jICvDAcl4J/AtUPlWagWdmRoP+UEaFXOytf0/ww== - -"@mgeist/libuv-monitor-linux-arm64@0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-linux-arm64/-/libuv-monitor-linux-arm64-0.0.3.tgz#ad7da0b88010f405864a911939910af3ee59f0e6" - integrity sha512-l8uK50x8c72/GpRzdE3LwlDCtb87VozWBuvo64uIzBtR53e3NLs45FJIIHiqQSEBY6fM0FI+jTRRV69pqN5j4A== - -"@mgeist/libuv-monitor-linux-x64@0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-linux-x64/-/libuv-monitor-linux-x64-0.0.3.tgz#0865de01775caca40ac8909cc60f7658c16e4e99" - integrity sha512-BMhlft+AhTZGsaEEFXIbYGHrA/aZv4FQbzWqbouHf5CpM1lXSW8/6+IA5RyIOz4yddRQLx5S/LVjdw21+k4Ptw== +"@mgeist/libuv-monitor-darwin-arm64@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-darwin-arm64/-/libuv-monitor-darwin-arm64-0.0.5.tgz#b12abf98c21531aa1551f1e215d26b05acb91b8f" + integrity sha512-6NAFV/qc0LBlADddDKSxXiHQLwMi08Z0d+VsbhmXNoT+jY1P3zAILV8/sjiRqpmdsk40/sw6bERTYaPJAHhp2Q== + +"@mgeist/libuv-monitor-darwin-x64@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-darwin-x64/-/libuv-monitor-darwin-x64-0.0.5.tgz#a78c6656df0f410b9a27c0b1e66f5f1b0efe2a2c" + integrity sha512-AQ1PynXr6nokFmH1odMrr6+xpjNiEHR0HvMZ/zwY4LrTslX196j6effVvAtuMWe3Rc18LRwpvXHSVQ+uDB8coQ== + +"@mgeist/libuv-monitor-linux-arm64@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-linux-arm64/-/libuv-monitor-linux-arm64-0.0.5.tgz#db3aa925cf434904ebbb2b37767e89cc511745e7" + integrity sha512-jCjrh+eHjsuuNVA7rQgGfMuk1xK8ZT5ek2agGVGmAGnQz+csppHH1MZHDCow23y22gQl9zcyhv6THsVYvTHFAg== + +"@mgeist/libuv-monitor-linux-x64@0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@mgeist/libuv-monitor-linux-x64/-/libuv-monitor-linux-x64-0.0.5.tgz#0c0131e50553a4d3cb6d8a1d2fa7250a8f361bbb" + integrity sha512-3pkn9m6xLyNmoECZbNDq2niX5a5OX5DLjvqsFMZglC/8Sz3a38qyPnyOwnjXrVcEF+DB46a2BQqEAPlphuEM8A== "@napi-rs/blake-hash-android-arm64@1.3.3": version "1.3.3" @@ -7768,15 +7768,15 @@ libnpmpublish@^6.0.4: semver "^7.3.7" ssri "^9.0.0" -libuv-monitor@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/libuv-monitor/-/libuv-monitor-0.0.3.tgz#72d6076d88b4906a6ef83ef43ee2ea026e092b27" - integrity sha512-IN/SjTpCr+E3v2+WAYNftcyBSLYl3sksrKPup9WlPLapQisPRdv6q2acGNmoIdv7GitED/Nzpuip0orH3lLhEA== +libuv-monitor@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/libuv-monitor/-/libuv-monitor-0.0.5.tgz#9daf0fb43abcd020d5302cd07ecf57226a0b52d4" + integrity sha512-se7RwDO/WN4ilFj7lRMhyCSqhDL3i6dj+CAAZyHPjy4LsNDWymS/51xaCKYGg1zrqMCbp4Ywy21PTR1hK758Iw== optionalDependencies: - "@mgeist/libuv-monitor-darwin-arm64" "0.0.3" - "@mgeist/libuv-monitor-darwin-x64" "0.0.3" - "@mgeist/libuv-monitor-linux-arm64" "0.0.3" - "@mgeist/libuv-monitor-linux-x64" "0.0.3" + "@mgeist/libuv-monitor-darwin-arm64" "0.0.5" + "@mgeist/libuv-monitor-darwin-x64" "0.0.5" + "@mgeist/libuv-monitor-linux-arm64" "0.0.5" + "@mgeist/libuv-monitor-linux-x64" "0.0.5" lines-and-columns@^1.1.6: version "1.1.6" From f244a7a2756672e6f936cc3051387e1d280ede34 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:02:25 -0700 Subject: [PATCH 20/37] moves broadcastTransaction into RpcClient chain namespace (#4061) all other rpc routes defined on the RpcClient are found within their namespaces. only 'chain/broadcastTransaction' is used directly without its namespace. --- ironfish-cli/src/commands/chain/broadcast.ts | 2 +- ironfish/src/rpc/clients/client.ts | 18 +++++++++--------- .../routes/chain/broadcastTransaction.test.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ironfish-cli/src/commands/chain/broadcast.ts b/ironfish-cli/src/commands/chain/broadcast.ts index 9a4521aba0..6de3769f25 100644 --- a/ironfish-cli/src/commands/chain/broadcast.ts +++ b/ironfish-cli/src/commands/chain/broadcast.ts @@ -26,7 +26,7 @@ export class BroadcastCommand extends IronfishCommand { CliUx.ux.action.start(`Broadcasting transaction`) const client = await this.sdk.connectRpc() - const response = await client.broadcastTransaction({ transaction }) + const response = await client.chain.broadcastTransaction({ transaction }) if (response.content) { CliUx.ux.action.stop(`Transaction broadcasted: ${response.content.hash}`) } diff --git a/ironfish/src/rpc/clients/client.ts b/ironfish/src/rpc/clients/client.ts index 2a6dc14c25..4e416dcdab 100644 --- a/ironfish/src/rpc/clients/client.ts +++ b/ironfish/src/rpc/clients/client.ts @@ -703,6 +703,15 @@ export abstract class RpcClient { params, ).waitForEnd() }, + + broadcastTransaction: ( + params: BroadcastTransactionRequest, + ): Promise> => { + return this.request( + `${ApiNamespace.chain}/broadcastTransaction`, + params, + ).waitForEnd() + }, } config = { @@ -740,13 +749,4 @@ export abstract class RpcClient { ).waitForEnd() }, } - - async broadcastTransaction( - params: BroadcastTransactionRequest, - ): Promise> { - return this.request( - `${ApiNamespace.chain}/broadcastTransaction`, - params, - ).waitForEnd() - } } diff --git a/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts b/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts index 37628a2697..c7806f50f8 100644 --- a/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts +++ b/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts @@ -13,7 +13,7 @@ describe('Route chain/broadcastTransaction', () => { const broadcastSpy = jest.spyOn(routeTest.peerNetwork, 'broadcastTransaction') - const response = await routeTest.client.broadcastTransaction({ + const response = await routeTest.client.chain.broadcastTransaction({ transaction: transaction.serialize().toString('hex'), }) @@ -24,7 +24,7 @@ describe('Route chain/broadcastTransaction', () => { it("should return an error if the transaction won't deserialize", async () => { await expect( - routeTest.client.broadcastTransaction({ + routeTest.client.chain.broadcastTransaction({ transaction: '0xdeadbeef', }), ).rejects.toThrow('Out of bounds read') From 98c580901d8ff0eae70e0c5bf7ccda75066e8019 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Mon, 10 Jul 2023 15:06:33 -0700 Subject: [PATCH 21/37] Add encoder name to DecoderFailed I think it makes sense for the DecoderFailed error to contain the name of the decoder that it was produced from. It also makes the code a bit simpler when you are relying on accumulating errors, but need a new wrapper type because you lose the encoder name in the error. --- .../src/wallet/account/encoder/account.ts | 19 ++++++++++--------- ironfish/src/wallet/account/encoder/bech32.ts | 6 +++++- .../src/wallet/account/encoder/bech32json.ts | 5 ++++- .../src/wallet/account/encoder/encoder.ts | 9 ++++++++- ironfish/src/wallet/account/encoder/json.ts | 2 +- .../src/wallet/account/encoder/mnemonic.ts | 2 +- .../src/wallet/account/encoder/spendingKey.ts | 5 ++++- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/ironfish/src/wallet/account/encoder/account.ts b/ironfish/src/wallet/account/encoder/account.ts index 066b4d8ce8..e3a5aa23ae 100644 --- a/ironfish/src/wallet/account/encoder/account.ts +++ b/ironfish/src/wallet/account/encoder/account.ts @@ -46,23 +46,24 @@ export function decodeAccount( value: string, options: AccountDecodingOptions = {}, ): AccountImport { - let decoded = null - const errors: { name: string; err: Error }[] = [] + const errors: DecodeFailed[] = [] + for (const encoder of ENCODER_VERSIONS) { try { - decoded = new encoder().decode(value, options) + const decoded = new encoder().decode(value, options) + + if (decoded) { + return decoded + } } catch (e) { if (e instanceof DecodeFailed) { - errors.push({ name: encoder.name, err: e as Error }) - continue + errors.push(e) } else { throw e } } - if (decoded) { - return decoded - } } - const errorString = errors.map((error) => `${error.name}: ${error.err.message}`).join('\n') + + const errorString = errors.map((error) => `${error.decoder}: ${error.message}`).join('\n') throw new Error(`Account could not be decoded, decoder errors:\n${errorString} `) } diff --git a/ironfish/src/wallet/account/encoder/bech32.ts b/ironfish/src/wallet/account/encoder/bech32.ts index b483ea2a94..f23d79ef0c 100644 --- a/ironfish/src/wallet/account/encoder/bech32.ts +++ b/ironfish/src/wallet/account/encoder/bech32.ts @@ -42,6 +42,7 @@ export class Bech32Encoder implements AccountEncoder { if (!hexEncoding) { throw new DecodeFailed( `Could not decode account ${value} using bech32: ${err?.message || ''}`, + this.constructor.name, ) } @@ -84,7 +85,10 @@ export class Bech32Encoder implements AccountEncoder { } } catch (e) { if (e instanceof EncodingError) { - throw new DecodeFailed(`Bufio decoding failed while using bech32 encoder: ${e.message}`) + throw new DecodeFailed( + `Bufio decoding failed while using bech32 encoder: ${e.message}`, + this.constructor.name, + ) } throw e } diff --git a/ironfish/src/wallet/account/encoder/bech32json.ts b/ironfish/src/wallet/account/encoder/bech32json.ts index 66ab4d6be2..40da2d0c1b 100644 --- a/ironfish/src/wallet/account/encoder/bech32json.ts +++ b/ironfish/src/wallet/account/encoder/bech32json.ts @@ -16,7 +16,10 @@ export class Bech32JsonEncoder implements AccountEncoder { decode(value: string, options?: AccountDecodingOptions): AccountImport { const [decoded, err] = Bech32m.decode(value) if (!decoded) { - throw new DecodeFailed(`Invalid bech32 JSON encoding: ${err?.message || ''}`) + throw new DecodeFailed( + `Invalid bech32 JSON encoding: ${err?.message || ''}`, + this.constructor.name, + ) } const accountImport = new JsonEncoder().decode(decoded) return { diff --git a/ironfish/src/wallet/account/encoder/encoder.ts b/ironfish/src/wallet/account/encoder/encoder.ts index 90e64f4de2..df76ea3910 100644 --- a/ironfish/src/wallet/account/encoder/encoder.ts +++ b/ironfish/src/wallet/account/encoder/encoder.ts @@ -6,7 +6,14 @@ import { AccountImport } from '../../walletdb/accountValue' export class DecodeInvalid extends Error {} -export class DecodeFailed extends Error {} +export class DecodeFailed extends Error { + decoder: string + + constructor(message?: string, decoder?: string) { + super(message) + this.decoder = decoder ?? '' + } +} export enum AccountFormat { JSON = 'JSON', diff --git a/ironfish/src/wallet/account/encoder/json.ts b/ironfish/src/wallet/account/encoder/json.ts index ab9aed77ca..60fcd31e77 100644 --- a/ironfish/src/wallet/account/encoder/json.ts +++ b/ironfish/src/wallet/account/encoder/json.ts @@ -38,7 +38,7 @@ export class JsonEncoder implements AccountEncoder { validateAccount(accountImport) return accountImport } catch (e) { - throw new DecodeFailed(`Invalid JSON: ${(e as Error).message}`) + throw new DecodeFailed(`Invalid JSON: ${(e as Error).message}`, this.constructor.name) } } } diff --git a/ironfish/src/wallet/account/encoder/mnemonic.ts b/ironfish/src/wallet/account/encoder/mnemonic.ts index 1b3f84d316..8e51f90745 100644 --- a/ironfish/src/wallet/account/encoder/mnemonic.ts +++ b/ironfish/src/wallet/account/encoder/mnemonic.ts @@ -44,7 +44,7 @@ export class MnemonicEncoder implements AccountEncoder { language = LanguageUtils.languageCodeToKey(code) } if (language === null) { - throw new DecodeFailed('Invalid mnemonic') + throw new DecodeFailed('Invalid mnemonic', this.constructor.name) } if (!options.name) { throw new DecodeInvalid('Name option is required for mnemonic key encoder') diff --git a/ironfish/src/wallet/account/encoder/spendingKey.ts b/ironfish/src/wallet/account/encoder/spendingKey.ts index 2a2777d2ea..bd00852cfe 100644 --- a/ironfish/src/wallet/account/encoder/spendingKey.ts +++ b/ironfish/src/wallet/account/encoder/spendingKey.ts @@ -19,7 +19,10 @@ export class SpendingKeyEncoder implements AccountEncoder { try { key = generateKeyFromPrivateKey(spendingKey) } catch (e) { - throw new DecodeFailed(`Invalid spending key: ${(e as Error).message}`) + throw new DecodeFailed( + `Invalid spending key: ${(e as Error).message}`, + this.constructor.name, + ) } if (!options.name) { From b0be919a0e6d5aab81c93364a4f5502aca249d22 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:44:54 -0400 Subject: [PATCH 22/37] feat(ironfish): Remove `submitNewTransactionCreated` telemetry (#4063) --- ironfish/src/node.ts | 4 ---- ironfish/src/telemetry/telemetry.ts | 31 ----------------------------- 2 files changed, 35 deletions(-) diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index 2c0cc03f02..a97f3ad30b 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -154,10 +154,6 @@ export class IronfishNode { incomingWebSocketWhitelist: config.getArray('incomingWebSocketWhitelist'), }) - this.wallet.onTransactionCreated.on((transaction) => { - this.telemetry.submitNewTransactionCreated(transaction, new Date()) - }) - this.miningManager.onNewBlock.on((block) => { this.telemetry.submitBlockMined(block) }) diff --git a/ironfish/src/telemetry/telemetry.ts b/ironfish/src/telemetry/telemetry.ts index c65efb15dd..d67dafecea 100644 --- a/ironfish/src/telemetry/telemetry.ts +++ b/ironfish/src/telemetry/telemetry.ts @@ -465,37 +465,6 @@ export class Telemetry { }) } - submitNewTransactionCreated(transaction: Transaction, seenAt: Date): void { - const hash = transaction.hash() - - if (!this.shouldSubmitTransaction(hash)) { - return - } - - this.submit({ - measurement: 'transaction_created', - timestamp: seenAt, - tags: [ - { - name: 'hash', - value: hash.toString('hex'), - }, - ], - fields: [ - { - name: 'notes', - type: 'integer', - value: transaction.notes.length, - }, - { - name: 'spends', - type: 'integer', - value: transaction.spends.length, - }, - ], - }) - } - submitNewTransactionSeen(transaction: Transaction, seenAt: Date): void { const hash = transaction.hash() From 6374917c34a99bef9b13206e4a853803ff8136b9 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Thu, 13 Jul 2023 13:04:55 -0700 Subject: [PATCH 23/37] Only pass in wallet to RPC helper function getAccount (#4062) --- ironfish/src/rpc/routes/faucet/getFunds.ts | 2 +- ironfish/src/rpc/routes/wallet/burnAsset.ts | 2 +- ironfish/src/rpc/routes/wallet/createTransaction.ts | 2 +- ironfish/src/rpc/routes/wallet/exportAccount.ts | 2 +- ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts | 2 +- ironfish/src/rpc/routes/wallet/getAccountTransaction.ts | 2 +- ironfish/src/rpc/routes/wallet/getAccountTransactions.ts | 2 +- ironfish/src/rpc/routes/wallet/getAssets.ts | 2 +- ironfish/src/rpc/routes/wallet/getBalance.ts | 2 +- ironfish/src/rpc/routes/wallet/getBalances.ts | 2 +- ironfish/src/rpc/routes/wallet/getNotes.ts | 2 +- ironfish/src/rpc/routes/wallet/getPublicKey.ts | 2 +- ironfish/src/rpc/routes/wallet/mintAsset.ts | 2 +- ironfish/src/rpc/routes/wallet/postTransaction.ts | 2 +- ironfish/src/rpc/routes/wallet/remove.ts | 2 +- ironfish/src/rpc/routes/wallet/rename.ts | 2 +- ironfish/src/rpc/routes/wallet/sendTransaction.ts | 2 +- ironfish/src/rpc/routes/wallet/use.ts | 2 +- ironfish/src/rpc/routes/wallet/utils.test.ts | 8 ++++---- ironfish/src/rpc/routes/wallet/utils.ts | 8 ++++---- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ironfish/src/rpc/routes/faucet/getFunds.ts b/ironfish/src/rpc/routes/faucet/getFunds.ts index f246add191..3cd551be82 100644 --- a/ironfish/src/rpc/routes/faucet/getFunds.ts +++ b/ironfish/src/rpc/routes/faucet/getFunds.ts @@ -37,7 +37,7 @@ router.register( throw new ResponseError('This endpoint is only available for testnet.', ERROR_CODES.ERROR) } - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const api = new WebApi({ getFundsEndpoint: node.config.get('getFundsApi'), diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.ts b/ironfish/src/rpc/routes/wallet/burnAsset.ts index 11d1372cea..1afafd3b9f 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.ts @@ -49,7 +49,7 @@ router.register( `${ApiNamespace.wallet}/burnAsset`, BurnAssetRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const fee = CurrencyUtils.decode(request.data.fee) const value = CurrencyUtils.decode(request.data.value) diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index 0a93b9ad1b..6ef8b223b6 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -103,7 +103,7 @@ router.register => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const params: Parameters[0] = { account: account, diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.ts b/ironfish/src/rpc/routes/wallet/exportAccount.ts index f00c726052..d3132689c7 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.ts @@ -38,7 +38,7 @@ router.register( `${ApiNamespace.wallet}/exportAccount`, ExportAccountRequestSchema, (request, node): void => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const { id: _, ...accountInfo } = account.serialize() if (request.data.viewOnly) { accountInfo.spendingKey = null diff --git a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts index 945fb9a1ec..fa07ab98c0 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts @@ -25,7 +25,7 @@ router.register => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) for await (const transaction of account.getTransactionsByTime()) { if (request.closed) { diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts index 670737eb50..c90f9acd50 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts @@ -90,7 +90,7 @@ router.register => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const transactionHash = Buffer.from(request.data.hash, 'hex') diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts b/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts index 02893d4fd1..1ddb22a232 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts @@ -99,7 +99,7 @@ router.register => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const headSequence = (await account.getHead())?.sequence ?? null diff --git a/ironfish/src/rpc/routes/wallet/getAssets.ts b/ironfish/src/rpc/routes/wallet/getAssets.ts index ef027ecd49..0c9b4e47ef 100644 --- a/ironfish/src/rpc/routes/wallet/getAssets.ts +++ b/ironfish/src/rpc/routes/wallet/getAssets.ts @@ -50,7 +50,7 @@ router.register( `${ApiNamespace.wallet}/getAssets`, GetAssetsRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) for await (const asset of account.getAssets()) { if (request.closed) { diff --git a/ironfish/src/rpc/routes/wallet/getBalance.ts b/ironfish/src/rpc/routes/wallet/getBalance.ts index 4d4d861e2f..f79b42a66d 100644 --- a/ironfish/src/rpc/routes/wallet/getBalance.ts +++ b/ironfish/src/rpc/routes/wallet/getBalance.ts @@ -63,7 +63,7 @@ router.register( async (request, node): Promise => { const confirmations = request.data?.confirmations ?? node.config.get('confirmations') - const account = getAccount(node, request.data?.account) + const account = getAccount(node.wallet, request.data?.account) let assetId = Asset.nativeId() if (request.data?.assetId) { diff --git a/ironfish/src/rpc/routes/wallet/getBalances.ts b/ironfish/src/rpc/routes/wallet/getBalances.ts index 33d66cf18b..094f97e7eb 100644 --- a/ironfish/src/rpc/routes/wallet/getBalances.ts +++ b/ironfish/src/rpc/routes/wallet/getBalances.ts @@ -73,7 +73,7 @@ router.register( `${ApiNamespace.wallet}/getBalances`, GetBalancesRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const balances = [] for await (const balance of node.wallet.getBalances(account, request.data.confirmations)) { diff --git a/ironfish/src/rpc/routes/wallet/getNotes.ts b/ironfish/src/rpc/routes/wallet/getNotes.ts index 2d7a6dacd6..95c9a5a42f 100644 --- a/ironfish/src/rpc/routes/wallet/getNotes.ts +++ b/ironfish/src/rpc/routes/wallet/getNotes.ts @@ -72,7 +72,7 @@ router.register( `${ApiNamespace.wallet}/getNotes`, GetNotesRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const pageSize = request.data.pageSize ?? DEFAULT_PAGE_SIZE const pageCursor = request.data.pageCursor diff --git a/ironfish/src/rpc/routes/wallet/getPublicKey.ts b/ironfish/src/rpc/routes/wallet/getPublicKey.ts index 586ab3107b..10f88ba4b2 100644 --- a/ironfish/src/rpc/routes/wallet/getPublicKey.ts +++ b/ironfish/src/rpc/routes/wallet/getPublicKey.ts @@ -25,7 +25,7 @@ router.register( `${ApiNamespace.wallet}/getPublicKey`, GetPublicKeyRequestSchema, (request, node): void => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) request.end({ account: account.name, diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.ts b/ironfish/src/rpc/routes/wallet/mintAsset.ts index 872de82a46..4b2c032465 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.ts @@ -55,7 +55,7 @@ router.register( `${ApiNamespace.wallet}/mintAsset`, MintAssetRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const fee = CurrencyUtils.decode(request.data.fee) const value = CurrencyUtils.decode(request.data.value) diff --git a/ironfish/src/rpc/routes/wallet/postTransaction.ts b/ironfish/src/rpc/routes/wallet/postTransaction.ts index 44ac190785..1649f53f41 100644 --- a/ironfish/src/rpc/routes/wallet/postTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/postTransaction.ts @@ -36,7 +36,7 @@ router.register( `${ApiNamespace.wallet}/postTransaction`, PostTransactionRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) const bytes = Buffer.from(request.data.transaction, 'hex') const raw = RawTransactionSerde.deserialize(bytes) diff --git a/ironfish/src/rpc/routes/wallet/remove.ts b/ironfish/src/rpc/routes/wallet/remove.ts index ce58217b10..89609178c9 100644 --- a/ironfish/src/rpc/routes/wallet/remove.ts +++ b/ironfish/src/rpc/routes/wallet/remove.ts @@ -26,7 +26,7 @@ router.register( `${ApiNamespace.wallet}/remove`, RemoveAccountRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) if (!request.data.confirm) { const balances = await account.getUnconfirmedBalances() diff --git a/ironfish/src/rpc/routes/wallet/rename.ts b/ironfish/src/rpc/routes/wallet/rename.ts index 4018d2b6f6..7c5be3445c 100644 --- a/ironfish/src/rpc/routes/wallet/rename.ts +++ b/ironfish/src/rpc/routes/wallet/rename.ts @@ -23,7 +23,7 @@ router.register( `${ApiNamespace.wallet}/rename`, RenameAccountRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) await account.setName(request.data.newName) request.end() }, diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.ts index a6dfa7ce3e..e0fe7b2123 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.ts @@ -67,7 +67,7 @@ router.register( `${ApiNamespace.wallet}/sendTransaction`, SendTransactionRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) if (!node.peerNetwork.isReady) { throw new ValidationError( diff --git a/ironfish/src/rpc/routes/wallet/use.ts b/ironfish/src/rpc/routes/wallet/use.ts index 772d9b8f7b..350f004b54 100644 --- a/ironfish/src/rpc/routes/wallet/use.ts +++ b/ironfish/src/rpc/routes/wallet/use.ts @@ -22,7 +22,7 @@ router.register( `${ApiNamespace.wallet}/use`, UseAccountRequestSchema, async (request, node): Promise => { - const account = getAccount(node, request.data.account) + const account = getAccount(node.wallet, request.data.account) await node.wallet.setDefaultAccount(account.name) request.end() }, diff --git a/ironfish/src/rpc/routes/wallet/utils.test.ts b/ironfish/src/rpc/routes/wallet/utils.test.ts index 06dfa785a7..e59ac5a0d0 100644 --- a/ironfish/src/rpc/routes/wallet/utils.test.ts +++ b/ironfish/src/rpc/routes/wallet/utils.test.ts @@ -20,12 +20,12 @@ describe('Accounts utils', () => { it('should fail if account is not found with name', () => { expect(() => { - getAccount(routeTest.node, 'badAccount') + getAccount(routeTest.node.wallet, 'badAccount') }).toThrow('No account with name') }) it('should pass if account is found with name', () => { - const result = getAccount(routeTest.node, name) + const result = getAccount(routeTest.node.wallet, name) expect(result.name).toEqual(name) expect(result.publicAddress).toEqual(publicAddress) }) @@ -34,13 +34,13 @@ describe('Accounts utils', () => { await routeTest.node.wallet.setDefaultAccount(null) expect(() => { - getAccount(routeTest.node) + getAccount(routeTest.node.wallet) }).toThrow('No account is currently active') }) it('should pass if default account is found', async () => { await routeTest.node.wallet.setDefaultAccount(name) - const result = getAccount(routeTest.node) + const result = getAccount(routeTest.node.wallet) expect(result.name).toEqual(name) expect(result.publicAddress).toEqual(publicAddress) }) diff --git a/ironfish/src/rpc/routes/wallet/utils.ts b/ironfish/src/rpc/routes/wallet/utils.ts index a073a76a44..6b96b39dc2 100644 --- a/ironfish/src/rpc/routes/wallet/utils.ts +++ b/ironfish/src/rpc/routes/wallet/utils.ts @@ -4,7 +4,7 @@ import { IronfishNode } from '../../../node' import { Note } from '../../../primitives' import { CurrencyUtils } from '../../../utils' -import { Account } from '../../../wallet' +import { Account, Wallet } from '../../../wallet' import { AccountImport } from '../../../wallet/walletdb/accountValue' import { AssetValue } from '../../../wallet/walletdb/assetValue' import { DecryptedNoteValue } from '../../../wallet/walletdb/decryptedNoteValue' @@ -17,16 +17,16 @@ import { RpcWalletNote, } from './types' -export function getAccount(node: IronfishNode, name?: string): Account { +export function getAccount(wallet: Wallet, name?: string): Account { if (name) { - const account = node.wallet.getAccountByName(name) + const account = wallet.getAccountByName(name) if (account) { return account } throw new ValidationError(`No account with name ${name}`) } - const defaultAccount = node.wallet.getDefaultAccount() + const defaultAccount = wallet.getDefaultAccount() if (defaultAccount) { return defaultAccount } From 02c154227f9e5bcfc6f72afa8c4ee860b29c06fb Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 14 Jul 2023 09:35:41 -0700 Subject: [PATCH 24/37] adds transactions to mempool in broadcastTransaction rpc (#4060) * adds transactions to mempool in broadcastTransaction rpc the chain/broadcastTransaction RPC endpoint can be used to prompt a node to broadcast a valid posted transaction over the network. however, the endpoint does not add the transaction to the node's mempool before broadcasting. the transaction should be added to the mempool for parity with the wallet's broadcast implementation. * updates use of client broadcastTransaction in test --- .../broadcastTransaction.test.ts.fixture | 17 +++++++++++++++++ .../routes/chain/broadcastTransaction.test.ts | 14 ++++++++++++++ .../rpc/routes/chain/broadcastTransaction.ts | 1 + 3 files changed, 32 insertions(+) diff --git a/ironfish/src/rpc/routes/chain/__fixtures__/broadcastTransaction.test.ts.fixture b/ironfish/src/rpc/routes/chain/__fixtures__/broadcastTransaction.test.ts.fixture index e002c5bdfe..02cdef6056 100644 --- a/ironfish/src/rpc/routes/chain/__fixtures__/broadcastTransaction.test.ts.fixture +++ b/ironfish/src/rpc/routes/chain/__fixtures__/broadcastTransaction.test.ts.fixture @@ -15,5 +15,22 @@ "type": "Buffer", "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA/GXGfWyGCX0yMtCZHvORDCvSIEJguFeAhdj94jB5sgeokJ11N0MmJtJKRIaLTo5NrC+xKU2logSZdHloVfnpiWQurqPHUZXJ/M6kXeRuAZCU1j1e9c2W2EKhog6g3D4ojvTPQIQD4y0QkcabJusfXBJl4S9jZLou/sfwHHHWt6USIDwCKzSslxotfQCQH7v2e1oAbtRjyZJv3fSN9/9ekrjNDCEVKK6bH1ghtYsYW5aK70snubOOMhekyAI8FqKaY8KpCX38GSvTZ3P2Nb1JrNMJajlzzP2ODxZIWshxvoT2pkbSyU2/LmenXGBB+mShSjRuSNz+93QYrRHwk+EUozFD8jK3w+dNMbtA01+kN4Zq/GieTqa77OMIh0I5GbdWFUfnGl8xUwxG10hH8bOwIdp/CPcv7It3Gyvhl8ju4YH7S3EJU3GFvajuPwQdKo3V6j/Za0nyCOu1D/a6C/55kIerK6wgTGos9ZN4PCZLCGgSEfcCF3ShD0ASeAoy3TKw6bxF90Nuy/mZlCMAJSILaW11RGu9l5/zO+2dr5+PMpBxCVcdpHXuWRXa1DCVp0TalD2O4x6uKHN91sKpNCjaUvTeURwLmnysieDZ7idv15/cMiOTkjKuRElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwrTERSirkmzkkCqss7/3XlrQSCBga2y2TjUS1B6jjgRhDowTxF1GxdYPvbpkVXO/IQpGpaiWG1S0wxphRhQOXAA==" } + ], + "Route chain/broadcastTransaction should add the transaction to the mempool": [ + { + "version": 2, + "id": "11786fb6-649d-420c-9e49-46035b7a7260", + "name": "test", + "spendingKey": "028cfbbf5fc6aff746b015fa06657a312cfb3faac4ea21547c2febe5b1945146", + "viewKey": "6af02801c28f970c04d968b1f6a96bc4f7f4303920e659a4ac81d4a66e4c433da3d1df1e3aec2b00d4d74da9e2fd4977a53f5dfbe36cfbe43386e2e8be44ec4f", + "incomingViewKey": "f839c08bdc9d1af94caa04b675430321a64e31f356fed21b89808143e9b27e01", + "outgoingViewKey": "bce272ea4b0dccc91013c5a1e4f070385554a39594dbdab56273b81fcfe699ed", + "publicAddress": "5843a659f17512db09bc5007063b7838a37d4f687cdadc514a9dd3671959cf6e", + "createdAt": null + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAPYcDdsqrmVpiQdEQpTXjV+bcIuroY4J9btGJlow+PrWCB3wlOJab8gw8kQKwqCOtifamYi2vJUKPqewzdyO3fCXLHaZcy6+hPvU887AHC/uwkk6yeyKlMxgwhWXGgM6vE2R+9dhUylmOhO92jZA3wZpbfQRxuzKXNHMOF0mHeN8PO/CjtAFjr78H2KpY+8kCsV1/BGQQxV34LX1+cYFPvUTVPOaIrcdiGtCmAW2NvG6zK0RPr0Xw0ur1Xs3K/tAhPrD4WzSQdydrrJdu7HyiTai3uAx8PhJAicchjg8pps1/F/d9nrtfxHKL70AL5XyYTnIUE4BLewxVpMs+xJ8dzK9bdxFpACyK2I238LhwH+Q9RJEip0ad8jGvq8wxHEgFJwWgFBcurGz+3sTPRczDH/DciCqffr/AvdJfDLszdwx9mzhCY6o5ahGoOgS37e2a/l9oIdV8yce1+XHSTx1+lK6lmX2F3H/k1232Ld8D5a/LiE4WYJwug0N6v3rRrtLSurnZBnxAB/QxRG91cmhHYwz1bGdtKMDrYSna469w6pQN+kVy6HzMj/oB0QdBmNhjcQ1BK6ghOqaF8FNRRTfOVquayM93fRwJRePQicEYqD4pMZ6u/a+bMElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwdE/ZCBSMiHQjW07vmp1Zmpvc+zlIDHxkH3e3X6dY1IqyZRE8FnoLobblnlQUYDdt3pHE9JYw03O33dBhZhbqAw==" + } ] } \ No newline at end of file diff --git a/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts b/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts index c7806f50f8..76958b059d 100644 --- a/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts +++ b/ironfish/src/rpc/routes/chain/broadcastTransaction.test.ts @@ -22,6 +22,20 @@ describe('Route chain/broadcastTransaction', () => { expect(broadcastSpy).toHaveBeenCalled() }) + it('should add the transaction to the mempool', async () => { + const transaction = await useMinersTxFixture(routeTest.wallet) + + const acceptSpy = jest.spyOn(routeTest.node.memPool, 'acceptTransaction') + + const response = await routeTest.client.chain.broadcastTransaction({ + transaction: transaction.serialize().toString('hex'), + }) + + expect(response.status).toBe(200) + expect(response.content?.hash).toEqual(transaction.hash().toString('hex')) + expect(acceptSpy).toHaveBeenCalled() + }) + it("should return an error if the transaction won't deserialize", async () => { await expect( routeTest.client.chain.broadcastTransaction({ diff --git a/ironfish/src/rpc/routes/chain/broadcastTransaction.ts b/ironfish/src/rpc/routes/chain/broadcastTransaction.ts index d0036591f0..8fbf09a780 100644 --- a/ironfish/src/rpc/routes/chain/broadcastTransaction.ts +++ b/ironfish/src/rpc/routes/chain/broadcastTransaction.ts @@ -41,6 +41,7 @@ router.register Date: Fri, 14 Jul 2023 12:48:45 -0400 Subject: [PATCH 25/37] feat(ironfish): Query account instead of chain when burning asset (#4066) --- ironfish/src/rpc/routes/wallet/burnAsset.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.ts b/ironfish/src/rpc/routes/wallet/burnAsset.ts index 1afafd3b9f..5070b39581 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.ts @@ -55,8 +55,8 @@ router.register( const value = CurrencyUtils.decode(request.data.value) const assetId = Buffer.from(request.data.assetId, 'hex') - const asset = await node.chain.getAssetById(assetId) - Assert.isNotNull(asset) + const asset = await account.getAsset(assetId) + Assert.isNotUndefined(asset) const transaction = await node.wallet.burn( account, From 3fb43a122dbfeb60621f67fb7e0a61a35c99dd80 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:49:00 -0400 Subject: [PATCH 26/37] feat(ironfish): Query account in `getAssetBalanceDeltas` (#4068) --- ironfish/src/rpc/routes/wallet/getAccountTransaction.ts | 2 +- ironfish/src/rpc/routes/wallet/getAccountTransactions.ts | 2 +- ironfish/src/rpc/routes/wallet/utils.ts | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts index c90f9acd50..f1dda046fd 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts @@ -105,7 +105,7 @@ router.register => { const serializedTransaction = serializeRpcAccountTransaction(transaction) - const assetBalanceDeltas = await getAssetBalanceDeltas(node, transaction) + const assetBalanceDeltas = await getAssetBalanceDeltas(account, transaction) let notes = undefined if (request.data.notes) { diff --git a/ironfish/src/rpc/routes/wallet/utils.ts b/ironfish/src/rpc/routes/wallet/utils.ts index 6b96b39dc2..c9cde1e146 100644 --- a/ironfish/src/rpc/routes/wallet/utils.ts +++ b/ironfish/src/rpc/routes/wallet/utils.ts @@ -68,15 +68,13 @@ export function deserializeRpcAccountImport(accountImport: RpcAccountImport): Ac } export async function getAssetBalanceDeltas( - node: IronfishNode, + account: Account, transaction: TransactionValue, ): Promise { const assetBalanceDeltas = new Array() for (const [assetId, delta] of transaction.assetBalanceDeltas.entries()) { - // TODO: update to use wallet assets store - const asset = await node.chain.getAssetById(assetId) - + const asset = await account.getAsset(assetId) const assetName = asset?.name.toString('hex') ?? '' assetBalanceDeltas.push({ From 0df5259c6d94211785fb4ca2f4c1311a495b1c59 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Fri, 14 Jul 2023 12:49:12 -0400 Subject: [PATCH 27/37] feat(ironfish): Query asset from account in `getAccountNotesStream` (#4067) --- ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts index fa07ab98c0..bbb6f88074 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts @@ -39,7 +39,7 @@ router.register Date: Fri, 14 Jul 2023 11:05:11 -0700 Subject: [PATCH 28/37] adds standalone wallet config options (#4064) * adds standalone wallet config options running a standalone wallet requires config options for the wallet's RPC connection to a node, for the wallet's RPC server, and for the wallet's worker pool adds config options to the Config store and sets default values: - sets default options for wallet node RPC connections to mirror node RPC server defaults - sets defaults for wallet RPC server to match defaults for node RPC server - uses separate default IPC filepath for wallet RPC server - uses separate default TLS files for wallet RPC server - uses distinct port for each TCP and HTTP server - uses same defaults for wallet worker pool as node worker pool * enableWalletScanning -> walletScanningEnabled --- ironfish/src/fileStores/config.ts | 106 ++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/ironfish/src/fileStores/config.ts b/ironfish/src/fileStores/config.ts index a7a51ef6ed..06b05a72dd 100644 --- a/ironfish/src/fileStores/config.ts +++ b/ironfish/src/fileStores/config.ts @@ -279,6 +279,66 @@ export type ConfigOptions = { * Always allow incoming connections from these IPs even if the node is at maxPeers */ incomingWebSocketWhitelist: string[] + + /** + * Enable the node's in-process wallet to scan blocks and mempool transactions + */ + walletScanningEnabled: boolean + + /** + * Enable standalone wallet process to connect to a node via IPC + */ + walletNodeIpcEnabled: boolean + walletNodeIpcPath: string + + /** + * Enable stanalone wallet process to connect to a node via TCP + */ + walletNodeTcpEnabled: boolean + walletNodeTcpHost: string + walletNodeTcpPort: number + walletNodeTlsEnabled: boolean + + /** + * Enable standalone wallet process to connect to a node via HTTP + */ + walletNodeHttpEnabled: boolean + walletNodeHttpHost: string + walletNodeHttpPort: number + + /** + * Enable IPC connections to a standalone wallet RPC server + */ + walletEnableRpcIpc: boolean + walletRpcIpcPath: string + + /** + * Enable TCP connections to a standalone wallet RPC server + */ + walletEnableRpcTcp: boolean + walletRpcTcpHost: string + walletRpcTcpPort: number + + /** + * Enable TLS for TCP connections to a standalone wallet RPC server + */ + walletEnableRpcTls: boolean + walletTlsKeyPath: string + walletTlsCertPath: string + + /** + * Enable HTTP connections to a standalone wallet RPC server + */ + walletEnableRpcHttp: boolean + walletRpcHttpHost: string + walletRpcHttpPort: number + + /** + * The number of CPU workers to use in the worker pool of a standalone wallet + * process. See config "nodeWorkers" + */ + walletWorkers: number + walletWorkersMax: number } export const ConfigOptionsSchema: yup.ObjectSchema> = yup @@ -358,6 +418,29 @@ export const ConfigOptionsSchema: yup.ObjectSchema> = yup memPoolRecentlyEvictedCacheSize: yup.number().integer(), networkDefinitionPath: yup.string().trim(), incomingWebSocketWhitelist: yup.array(yup.string().trim().defined()), + walletScanningEnabled: yup.boolean(), + walletNodeIpcEnabled: yup.boolean(), + walletNodeIpcPath: yup.string(), + walletNodeTcpEnabled: yup.boolean(), + walletNodeTcpHost: yup.string(), + walletNodeTcpPort: yup.number(), + walletNodeTlsEnabled: yup.boolean(), + walletNodeHttpEnabled: yup.boolean(), + walletNodeHttpHost: yup.string(), + walletNodeHttpPort: yup.number(), + walletEnableRpcIpc: yup.boolean(), + walletRpcIpcPath: yup.string(), + walletEnableRpcTcp: yup.boolean(), + walletRpcTcpHost: yup.string(), + walletRpcTcpPort: yup.number(), + walletEnableRpcTls: yup.boolean(), + walletTlsKeyPath: yup.string(), + walletTlsCertPath: yup.string(), + walletEnableRpcHttp: yup.boolean(), + walletRpcHttpHost: yup.string(), + walletRpcHttpPort: yup.number(), + walletWorkers: yup.number(), + walletWorkersMax: yup.number(), }) .defined() @@ -451,6 +534,29 @@ export class Config extends KeyStore { memPoolRecentlyEvictedCacheSize: 60000, networkDefinitionPath: files.resolve(files.join(dataDir, 'network.json')), incomingWebSocketWhitelist: [], + walletScanningEnabled: true, + walletNodeIpcEnabled: true, + walletNodeIpcPath: files.resolve(files.join(dataDir, 'ironfish.ipc')), + walletNodeTcpEnabled: false, + walletNodeTcpHost: 'localhost', + walletNodeTcpPort: 8020, + walletNodeTlsEnabled: true, + walletNodeHttpEnabled: false, + walletNodeHttpHost: 'localhost', + walletNodeHttpPort: 8021, + walletEnableRpcIpc: true, + walletRpcIpcPath: files.resolve(files.join(dataDir, 'ironfish.wallet.ipc')), + walletEnableRpcTcp: false, + walletRpcTcpHost: 'localhost', + walletRpcTcpPort: 8022, + walletEnableRpcTls: true, + walletTlsKeyPath: files.resolve(files.join(dataDir, 'certs', 'wallet-key.pem')), + walletTlsCertPath: files.resolve(files.join(dataDir, 'certs', 'wallet-cert.pem')), + walletEnableRpcHttp: false, + walletRpcHttpHost: 'localhost', + walletRpcHttpPort: 8023, + walletWorkers: -1, + walletWorkersMax: 6, } } } From 2b25847ce5de3a79f551ccf6d79270c8d15c68e4 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Fri, 14 Jul 2023 18:06:41 -0400 Subject: [PATCH 29/37] refactor(ironfish): Encapsulate block header DB operations (#4043) --- ironfish/src/blockchain/blockchain.ts | 45 +++++++------- .../src/blockchain/database/blockchaindb.ts | 62 +++++++++++++++++++ ironfish/src/blockchain/index.ts | 1 + 3 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 ironfish/src/blockchain/database/blockchaindb.ts diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 7720a7e860..1bd0cd033d 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -50,19 +50,17 @@ import { StringEncoding, U32_ENCODING, } from '../storage' -import { createDB } from '../storage/utils' import { Strategy } from '../strategy' import { AsyncUtils, BenchUtils, HashUtils } from '../utils' import { WorkerPool } from '../workerPool' import { AssetValue, AssetValueEncoding } from './database/assetValue' -import { HeaderEncoding } from './database/headers' +import { BlockchainDB } from './database/blockchaindb' import { SequenceToHashesValueEncoding } from './database/sequenceToHashes' import { TransactionsValueEncoding } from './database/transactions' import { NullifierSet } from './nullifierSet/nullifierSet' import { AssetSchema, HashToNextSchema, - HeadersSchema, MetaSchema, SequenceToHashesSchema, SequenceToHashSchema, @@ -83,6 +81,7 @@ export class Blockchain { consensus: Consensus seedGenesisBlock: SerializedBlock config: Config + blockchainDb: BlockchainDB synced = false opened = false @@ -104,8 +103,6 @@ export class Blockchain { // Contains flat fields meta: IDatabaseStore // BlockHash -> BlockHeader - headers: IDatabaseStore - // BlockHash -> BlockHeader transactions: IDatabaseStore // Sequence -> BlockHash[] sequenceToHashes: IDatabaseStore @@ -187,7 +184,6 @@ export class Blockchain { this.logger = logger.withTag('blockchain') this.metrics = options.metrics || new MetricsMonitor({ logger: this.logger }) this.verifier = new Verifier(this, options.workerPool) - this.db = createDB({ location: options.location }) this.addSpeed = new RollingAverage(500) this.invalid = new LRU(100, null, BufferMap) this.orphans = new LRU(100, null, BufferMap) @@ -197,6 +193,14 @@ export class Blockchain { this.seedGenesisBlock = options.genesis this.config = options.config + this.blockchainDb = new BlockchainDB({ + files: options.files, + location: options.location, + }) + // TODO(rohanjadvani): This is temporary to reduce pull request sizes and + // will be removed once all stores are migrated + this.db = this.blockchainDb.db + // Flat Fields this.meta = this.db.addStore({ name: 'bm', @@ -204,13 +208,6 @@ export class Blockchain { valueEncoding: BUFFER_ENCODING, }) - // BlockHash -> BlockHeader - this.headers = this.db.addStore({ - name: 'bh', - keyEncoding: BUFFER_ENCODING, - valueEncoding: new HeaderEncoding(), - }) - // BlockHash -> Transaction[] this.transactions = this.db.addStore({ name: 'bt', @@ -303,12 +300,13 @@ export class Blockchain { if (this.opened) { return } - this.opened = true - await this.files.mkdir(this.location, { recursive: true }) - await this.db.open() - await this.db.upgrade(VERSION_DATABASE_CHAIN) + this.opened = true + await this.blockchainDb.open() + await this.load() + } + private async load(): Promise { let genesisHeader = await this.getHeaderAtSequence(GENESIS_BLOCK_SEQUENCE) if (genesisHeader) { Assert.isTrue( @@ -362,8 +360,9 @@ export class Blockchain { if (!this.opened) { return } + this.opened = false - await this.db.close() + await this.blockchainDb.close() } async addBlock(block: Block): Promise<{ @@ -872,7 +871,7 @@ export class Blockchain { return this.db.withTransaction(tx, async (tx) => { const [header, transactions] = await Promise.all([ - blockHeader || this.headers.get(blockHash, tx).then((result) => result?.header), + blockHeader || this.blockchainDb.getBlockHeader(blockHash, tx), this.transactions.get(blockHash, tx), ]) @@ -894,7 +893,7 @@ export class Blockchain { * Returns true if the blockchain has a block at the given hash */ async hasBlock(hash: BlockHash, tx?: IDatabaseTransaction): Promise { - const header = await this.headers.get(hash, tx) + const header = await this.blockchainDb.getBlockHeader(hash, tx) return !!header } @@ -1034,7 +1033,7 @@ export class Blockchain { } async getHeader(hash: BlockHash, tx?: IDatabaseTransaction): Promise { - return (await this.headers.get(hash, tx))?.header || null + return (await this.blockchainDb.getBlockHeader(hash, tx)) || null } async getPrevious( @@ -1163,7 +1162,7 @@ export class Blockchain { } await this.transactions.del(hash, tx) - await this.headers.del(hash, tx) + await this.blockchainDb.deleteHeader(hash, tx) // TODO: use a new heads table to recalculate this if (this.latest.hash.equals(hash)) { @@ -1336,7 +1335,7 @@ export class Blockchain { const sequence = block.header.sequence // Update BlockHash -> BlockHeader - await this.headers.put(hash, { header: block.header }, tx) + await this.blockchainDb.putBlockHeader(hash, { header: block.header }, tx) // Update BlockHash -> Transaction await this.transactions.add(hash, { transactions: block.transactions }, tx) diff --git a/ironfish/src/blockchain/database/blockchaindb.ts b/ironfish/src/blockchain/database/blockchaindb.ts new file mode 100644 index 0000000000..245d6a1b3e --- /dev/null +++ b/ironfish/src/blockchain/database/blockchaindb.ts @@ -0,0 +1,62 @@ +/* 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 { FileSystem } from '../../fileSystems' +import { BlockHeader } from '../../primitives' +import { BUFFER_ENCODING, IDatabase, IDatabaseStore, IDatabaseTransaction } from '../../storage' +import { createDB } from '../../storage/utils' +import { HeadersSchema } from '../schema' +import { HeaderEncoding, HeaderValue } from './headers' + +export const VERSION_DATABASE_CHAIN = 14 + +export class BlockchainDB { + db: IDatabase + location: string + files: FileSystem + + // BlockHash -> BlockHeader + headers: IDatabaseStore + + constructor(options: { location: string; files: FileSystem }) { + this.location = options.location + this.files = options.files + this.db = createDB({ location: options.location }) + + // BlockHash -> BlockHeader + this.headers = this.db.addStore({ + name: 'bh', + keyEncoding: BUFFER_ENCODING, + valueEncoding: new HeaderEncoding(), + }) + } + + async open(): Promise { + await this.files.mkdir(this.location, { recursive: true }) + await this.db.open() + await this.db.upgrade(VERSION_DATABASE_CHAIN) + } + + async close(): Promise { + await this.db.close() + } + + async getBlockHeader( + blockHash: Buffer, + tx?: IDatabaseTransaction, + ): Promise { + return (await this.headers.get(blockHash, tx))?.header + } + + async deleteHeader(hash: Buffer, tx?: IDatabaseTransaction): Promise { + return this.headers.del(hash, tx) + } + + async putBlockHeader( + hash: Buffer, + header: HeaderValue, + tx?: IDatabaseTransaction, + ): Promise { + return this.headers.put(hash, header, tx) + } +} diff --git a/ironfish/src/blockchain/index.ts b/ironfish/src/blockchain/index.ts index 91faf90d18..1e7182478f 100644 --- a/ironfish/src/blockchain/index.ts +++ b/ironfish/src/blockchain/index.ts @@ -3,3 +3,4 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ export * from './blockchain' +export { VERSION_DATABASE_CHAIN } from './database/blockchaindb' From 72efbeee0c2450ce2e1f39c531eb38f2923e58e1 Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Fri, 7 Jul 2023 03:23:32 -0700 Subject: [PATCH 30/37] Verified assets: use a retry strategy when refreshing If fetching the assets fails (because e.g. of a network glitch), do not wait until the next refresh interval, but retry sooner. The retry strategy used here is: exponential backoff with an initial delay of 1 minute and a random jitter of 20%. This commit also upgrades to the version of jest, which is needed to use `jest.advanceTimersToNextTimerAsync()`. --- ironfish/package.json | 6 +- ironfish/src/assets/assetsVerifier.test.ts | 8 +- ironfish/src/assets/assetsVerifier.ts | 9 +- ironfish/src/utils/index.ts | 1 + ironfish/src/utils/retry.test.ts | 164 +++++ ironfish/src/utils/retry.ts | 64 ++ yarn.lock | 664 ++++++++++++++++++++- 7 files changed, 908 insertions(+), 8 deletions(-) create mode 100644 ironfish/src/utils/retry.test.ts create mode 100644 ironfish/src/utils/retry.ts diff --git a/ironfish/package.json b/ironfish/package.json index 81904434cb..546ab6b789 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -67,11 +67,11 @@ }, "devDependencies": { "@jest/reporters": "29.3.1", - "@jest/types": "29.3.1", + "@jest/types": "29.5.0", "@types/buffer-json": "2.0.0", "@types/colors": "1.2.1", "@types/imurmurhash": "0.1.1", - "@types/jest": "29.2.4", + "@types/jest": "29.5.2", "@types/leveldown": "4.0.2", "@types/levelup": "4.3.0", "@types/lodash": "4.14.170", @@ -89,7 +89,7 @@ "eslint-plugin-jest": "27.1.6", "eslint-plugin-prettier": "3.4.0", "eslint-plugin-react-hooks": "4.2.0", - "jest": "29.3.1", + "jest": "29.5.0", "jest-jasmine2": "29.3.1", "mitm": "1.7.2", "prettier": "2.3.2", diff --git a/ironfish/src/assets/assetsVerifier.test.ts b/ironfish/src/assets/assetsVerifier.test.ts index 6013b29223..4261b24448 100644 --- a/ironfish/src/assets/assetsVerifier.test.ts +++ b/ironfish/src/assets/assetsVerifier.test.ts @@ -155,7 +155,7 @@ describe('AssetsVerifier', () => { const warn = jest.spyOn(assetsVerifier['logger'], 'warn') assetsVerifier.start() - await waitForRefreshToFinish(refresh) + await expect(waitForRefreshToFinish(refresh)).rejects.toThrow() expect(warn).toHaveBeenCalledWith( 'Error while fetching verified assets: Request failed with status code 500', @@ -223,7 +223,7 @@ describe('AssetsVerifier', () => { expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' }) jest.runOnlyPendingTimers() - await waitForRefreshToFinish(refresh) + await expect(waitForRefreshToFinish(refresh)).rejects.toThrow() expect(warn).toHaveBeenCalledWith( 'Error while fetching verified assets: Request failed with status code 500', @@ -262,6 +262,8 @@ describe('AssetsVerifier', () => { const cache = Object.create( VerifiedAssetsCacheStore.prototype, ) as VerifiedAssetsCacheStore + jest.spyOn(cache, 'setMany').mockReturnValue(undefined) + jest.spyOn(cache, 'save').mockResolvedValue(undefined) cache.config = { apiUrl: 'https://test/assets/verified', assetIds: ['0123'], @@ -291,6 +293,8 @@ describe('AssetsVerifier', () => { const cache = Object.create( VerifiedAssetsCacheStore.prototype, ) as VerifiedAssetsCacheStore + jest.spyOn(cache, 'setMany').mockReturnValue(undefined) + jest.spyOn(cache, 'save').mockResolvedValue(undefined) cache.config = { apiUrl: 'https://foo.test/assets/verified', assetIds: ['0123'], diff --git a/ironfish/src/assets/assetsVerifier.ts b/ironfish/src/assets/assetsVerifier.ts index dce5a6e9ff..b16f89a828 100644 --- a/ironfish/src/assets/assetsVerifier.ts +++ b/ironfish/src/assets/assetsVerifier.ts @@ -5,6 +5,7 @@ import { VerifiedAssetsCacheStore } from '../fileStores/verifiedAssets' import { createRootLogger, Logger } from '../logger' import { ErrorUtils } from '../utils' import { SetIntervalToken } from '../utils' +import { Retry } from '../utils' import { AssetsVerificationApi, VerifiedAssets } from './assetsVerificationApi' export type AssetVerification = { @@ -17,6 +18,11 @@ export class AssetsVerifier { private readonly logger: Logger private readonly api: AssetsVerificationApi private readonly cache?: VerifiedAssetsCacheStore + private readonly retry = new Retry({ + delay: 60 * 1000, // 1 minute + jitter: 0.2, // 20% + maxDelay: 60 * 60 * 1000, // 1 hour + }) private started: boolean private refreshToken?: SetIntervalToken @@ -59,7 +65,7 @@ export class AssetsVerifier { } private async refreshLoop(): Promise { - await this.refresh() + await this.retry.try(this.refresh.bind(this)) this.refreshToken = setTimeout(() => { void this.refreshLoop() @@ -80,6 +86,7 @@ export class AssetsVerifier { } } catch (error) { this.logger.warn(`Error while fetching verified assets: ${ErrorUtils.renderError(error)}`) + throw error } } diff --git a/ironfish/src/utils/index.ts b/ironfish/src/utils/index.ts index 1c774339ee..27294306f1 100644 --- a/ironfish/src/utils/index.ts +++ b/ironfish/src/utils/index.ts @@ -2,6 +2,7 @@ * 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 './asset' +export * from './retry' export * from './array' export * from './async' export * from './bech32m' diff --git a/ironfish/src/utils/retry.test.ts b/ironfish/src/utils/retry.test.ts new file mode 100644 index 0000000000..6d36ca0e91 --- /dev/null +++ b/ironfish/src/utils/retry.test.ts @@ -0,0 +1,164 @@ +/* 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 { Retry } from './retry' + +describe('Retry', () => { + // `legacyFakeTimers` should be `false` by default according to the + // documentation, but somehow not specifying it explicitly results in legacy + // fake timers being used + jest.useFakeTimers({ legacyFakeTimers: false }) + + it('immediately returns when there is no error', async () => { + const fn = jest.fn().mockResolvedValue(123) + const retry = new Retry({ + delay: 1000, + jitter: 0.2, + maxDelay: 5000, + }) + + expect(await retry.try(fn)).toEqual(123) + expect(fn).toHaveBeenCalledTimes(1) + }) + + describe('without maxRetries', () => { + it('keeps retrying until the function succeeds', async () => { + const fn = jest.fn() + const retry = new Retry({ + delay: 1000, + jitter: 0.2, + maxDelay: 5000, + }) + + // simulate 100 failures before suceeding + const numFailures = 100 + for (let n = 1; n <= numFailures; n++) { + fn.mockRejectedValueOnce(`error #${n}`) + } + fn.mockResolvedValueOnce(123) + + // wait for all the failure invocations to complete + const promise = retry.try(fn) + for (let n = 1; n <= numFailures; n++) { + expect(fn).toHaveBeenCalledTimes(n) + await jest.advanceTimersToNextTimerAsync() + } + + // now it should succeed + expect(fn).toHaveBeenCalledTimes(numFailures + 1) + await expect(promise).resolves.toEqual(123) + }) + }) + + describe('with maxRetries', () => { + it('keeps retrying until the maximum number of retries is reached', async () => { + const maxRetries = 0 //10 + const fn = jest.fn() + const retry = new Retry({ + delay: 1000, + jitter: 0.2, + maxDelay: 5000, + maxRetries, + }) + + // simulate 100 failures before suceeding + const numFailures = 100 + for (let n = 1; n <= numFailures; n++) { + fn.mockRejectedValueOnce(`error #${n}`) + } + fn.mockResolvedValueOnce(123) + + // wait for 9 error invocations to complete + const promise = retry.try(fn) + for (let n = 1; n <= maxRetries; n++) { + expect(fn).toHaveBeenCalledTimes(n) + await jest.advanceTimersToNextTimerAsync() + } + + // now the promise should be rejected + expect(fn).toHaveBeenCalledTimes(maxRetries + 1) + await expect(promise).rejects.toBe('error #1') + }) + }) + + describe('nextDelay', () => { + describe('without jitter', () => { + it('returns multiples of powers of 2 bounded by maxDelay', () => { + const retry = new Retry({ + delay: 10, + jitter: 0, + maxDelay: 200, + }) + const nextDelay = retry['nextDelay'].bind(retry) + + expect(nextDelay()).toBe(10) + expect(nextDelay()).toBe(20) + expect(nextDelay()).toBe(40) + expect(nextDelay()).toBe(80) + expect(nextDelay()).toBe(160) + + expect(nextDelay()).toBe(200) + expect(nextDelay()).toBe(200) + expect(nextDelay()).toBe(200) + expect(nextDelay()).toBe(200) + }) + }) + + describe('with jitter', () => { + it('returns approximate multiples of powers of 2 bounded by maxDelay', () => { + const retry = new Retry({ + delay: 10, + jitter: 0.2, + maxDelay: 200, + }) + const nextDelay = retry['nextDelay'].bind(retry) + + const expectToBeInRange = (value: number, min: number, max: number) => { + expect(value).toBeGreaterThanOrEqual(min) + expect(value).toBeLessThanOrEqual(max) + } + + expectToBeInRange(nextDelay(), 8, 12) + expectToBeInRange(nextDelay(), 16, 24) + expectToBeInRange(nextDelay(), 32, 48) + expectToBeInRange(nextDelay(), 64, 96) + expectToBeInRange(nextDelay(), 128, 192) + + expect(nextDelay()).toBe(200) + expect(nextDelay()).toBe(200) + expect(nextDelay()).toBe(200) + expect(nextDelay()).toBe(200) + }) + + it('returns random bounded numbers', () => { + const retry = new Retry({ + delay: 10, + jitter: 0.2, + maxDelay: 200, + }) + const nextDelay = retry['nextDelay'].bind(retry) + + const deviations = [ + (nextDelay() - 10) / 10, + (nextDelay() - 20) / 10, + (nextDelay() - 40) / 10, + (nextDelay() - 80) / 10, + (nextDelay() - 160) / 10, + ] + + // check that the relative deviations from the exponential function are + // within the jitter value + for (const dev of deviations) { + expect(dev).toBeGreaterThanOrEqual(-0.2) + expect(dev).toBeLessThanOrEqual(0.2) + } + + // check that the variance is non-zero, i.e. that there is some + // randomness involved + const variance = + deviations.map((dev) => dev * dev).reduce((a, b) => a + b) / deviations.length + expect(variance).not.toBe(0) + }) + }) + }) +}) diff --git a/ironfish/src/utils/retry.ts b/ironfish/src/utils/retry.ts new file mode 100644 index 0000000000..8e60f78dbd --- /dev/null +++ b/ironfish/src/utils/retry.ts @@ -0,0 +1,64 @@ +/* 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 type RetryStrategy = { + delay: number + jitter: number + maxDelay: number + maxRetries?: number +} + +export class Retry { + private readonly strategy: RetryStrategy + + private attempt: number + + constructor(strategy: RetryStrategy) { + this.strategy = strategy + this.attempt = 0 + } + + try(fn: () => Promise): Promise { + return new Promise((resolve, reject) => { + this.tryExecutor(fn, resolve, reject) + }) + } + + private tryExecutor( + fn: () => Promise, + resolve: (result: T) => void, + reject: (error: E) => void, + ) { + fn() + .then((result) => { + this.reset() + resolve(result) + }) + .catch((error) => { + if (this.shouldRetry()) { + setTimeout(() => { + this.tryExecutor(fn, resolve, reject) + }, this.nextDelay()) + } else { + reject(error) + } + }) + } + + private reset() { + this.attempt = 0 + } + + private shouldRetry(): boolean { + return this.strategy.maxRetries === undefined || this.attempt < this.strategy.maxRetries + } + + private nextDelay(): number { + // exponential backoff + const delay = + this.strategy.delay * (2 ** this.attempt + this.strategy.jitter * Math.random()) + this.attempt += 1 + return Math.min(delay, this.strategy.maxDelay) + } +} diff --git a/yarn.lock b/yarn.lock index 4289964f6e..59e94631b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1678,6 +1678,18 @@ jest-util "^29.3.1" slash "^3.0.0" +"@jest/console@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.1.tgz#b48ba7b9c34b51483e6d590f46e5837f1ab5f639" + integrity sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.6.1" + jest-util "^29.6.1" + slash "^3.0.0" + "@jest/core@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.3.1.tgz#bff00f413ff0128f4debec1099ba7dcd649774a1" @@ -1712,6 +1724,40 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/core@^29.5.0", "@jest/core@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.1.tgz#fac0d9ddf320490c93356ba201451825231e95f6" + integrity sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ== + dependencies: + "@jest/console" "^29.6.1" + "@jest/reporters" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.5.0" + jest-config "^29.6.1" + jest-haste-map "^29.6.1" + jest-message-util "^29.6.1" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.1" + jest-resolve-dependencies "^29.6.1" + jest-runner "^29.6.1" + jest-runtime "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" + jest-watcher "^29.6.1" + micromatch "^4.0.4" + pretty-format "^29.6.1" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/environment@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.3.1.tgz#eb039f726d5fcd14698acd072ac6576d41cfcaa6" @@ -1722,6 +1768,16 @@ "@types/node" "*" jest-mock "^29.3.1" +"@jest/environment@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.1.tgz#ee358fff2f68168394b4a50f18c68278a21fe82f" + integrity sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A== + dependencies: + "@jest/fake-timers" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + jest-mock "^29.6.1" + "@jest/expect-utils@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.3.1.tgz#531f737039e9b9e27c42449798acb5bba01935b6" @@ -1729,6 +1785,13 @@ dependencies: jest-get-type "^29.2.0" +"@jest/expect-utils@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.1.tgz#ab83b27a15cdd203fe5f68230ea22767d5c3acc5" + integrity sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw== + dependencies: + jest-get-type "^29.4.3" + "@jest/expect@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.3.1.tgz#456385b62894349c1d196f2d183e3716d4c6a6cd" @@ -1737,6 +1800,14 @@ expect "^29.3.1" jest-snapshot "^29.3.1" +"@jest/expect@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.1.tgz#fef18265188f6a97601f1ea0a2912d81a85b4657" + integrity sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg== + dependencies: + expect "^29.6.1" + jest-snapshot "^29.6.1" + "@jest/fake-timers@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.3.1.tgz#b140625095b60a44de820876d4c14da1aa963f67" @@ -1749,6 +1820,18 @@ jest-mock "^29.3.1" jest-util "^29.3.1" +"@jest/fake-timers@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.1.tgz#c773efddbc61e1d2efcccac008139f621de57c69" + integrity sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg== + dependencies: + "@jest/types" "^29.6.1" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.6.1" + jest-mock "^29.6.1" + jest-util "^29.6.1" + "@jest/globals@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.3.1.tgz#92be078228e82d629df40c3656d45328f134a0c6" @@ -1759,6 +1842,16 @@ "@jest/types" "^29.3.1" jest-mock "^29.3.1" +"@jest/globals@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.1.tgz#c8a8923e05efd757308082cc22893d82b8aa138f" + integrity sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A== + dependencies: + "@jest/environment" "^29.6.1" + "@jest/expect" "^29.6.1" + "@jest/types" "^29.6.1" + jest-mock "^29.6.1" + "@jest/reporters@29.3.1", "@jest/reporters@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.3.1.tgz#9a6d78c109608e677c25ddb34f907b90e07b4310" @@ -1789,6 +1882,36 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/reporters@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.1.tgz#3325a89c9ead3cf97ad93df3a427549d16179863" + integrity sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.6.1" + jest-util "^29.6.1" + jest-worker "^29.6.1" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + "@jest/schemas@^29.0.0": version "29.0.0" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" @@ -1796,6 +1919,13 @@ dependencies: "@sinclair/typebox" "^0.24.1" +"@jest/schemas@^29.4.3", "@jest/schemas@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" + integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^29.2.0": version "29.2.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.2.0.tgz#ab3420c46d42508dcc3dc1c6deee0b613c235744" @@ -1805,6 +1935,15 @@ callsites "^3.0.0" graceful-fs "^4.2.9" +"@jest/source-map@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" + integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + "@jest/test-result@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.3.1.tgz#92cd5099aa94be947560a24610aa76606de78f50" @@ -1815,6 +1954,16 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" +"@jest/test-result@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.1.tgz#850e565a3f58ee8ca6ec424db00cb0f2d83c36ba" + integrity sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw== + dependencies: + "@jest/console" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-sequencer@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz#fa24b3b050f7a59d48f7ef9e0b782ab65123090d" @@ -1825,6 +1974,16 @@ jest-haste-map "^29.3.1" slash "^3.0.0" +"@jest/test-sequencer@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz#e3e582ee074dd24ea9687d7d1aaf05ee3a9b068e" + integrity sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg== + dependencies: + "@jest/test-result" "^29.6.1" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.1" + slash "^3.0.0" + "@jest/transform@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.3.1.tgz#1e6bd3da4af50b5c82a539b7b1f3770568d6e36d" @@ -1846,7 +2005,40 @@ slash "^3.0.0" write-file-atomic "^4.0.1" -"@jest/types@29.3.1", "@jest/types@^29.3.1": +"@jest/transform@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.1.tgz#acb5606019a197cb99beda3c05404b851f441c92" + integrity sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.1" + jest-regex-util "^29.4.3" + jest-util "^29.6.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jest/types@^29.3.1": version "29.3.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== @@ -1858,6 +2050,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.5.0", "@jest/types@^29.6.1": + version "29.6.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" + integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== + dependencies: + "@jest/schemas" "^29.6.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1911,6 +2115,14 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/trace-mapping@^0.3.18": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@lerna/add@6.4.1": version "6.4.1" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-6.4.1.tgz#fa20fe9ff875dc5758141262c8cde0d9a6481ec4" @@ -3443,6 +3655,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -3450,6 +3667,20 @@ dependencies: type-detect "4.0.8" +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" @@ -3637,6 +3868,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@29.5.2": + version "29.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b" + integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -4337,6 +4576,19 @@ babel-jest@^29.3.1: graceful-fs "^4.2.9" slash "^3.0.0" +babel-jest@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.1.tgz#a7141ad1ed5ec50238f3cd36127636823111233a" + integrity sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A== + dependencies: + "@jest/transform" "^29.6.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.5.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" @@ -4358,6 +4610,16 @@ babel-plugin-jest-hoist@^29.2.0: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + babel-preset-current-node-syntax@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" @@ -4384,6 +4646,14 @@ babel-preset-jest@^29.2.0: babel-plugin-jest-hoist "^29.2.0" babel-preset-current-node-syntax "^1.0.0" +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== + dependencies: + babel-plugin-jest-hoist "^29.5.0" + babel-preset-current-node-syntax "^1.0.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -5384,6 +5654,11 @@ diff-sequences@^29.3.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.3.1.tgz#104b5b95fe725932421a9c6e5b4bef84c3f2249e" integrity sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -5878,6 +6153,18 @@ expect@^29.0.0, expect@^29.3.1: jest-message-util "^29.3.1" jest-util "^29.3.1" +expect@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.1.tgz#64dd1c8f75e2c0b209418f2b8d36a07921adfdf1" + integrity sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g== + dependencies: + "@jest/expect-utils" "^29.6.1" + "@types/node" "*" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-util "^29.6.1" + external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -7140,6 +7427,14 @@ jest-changed-files@^29.2.0: execa "^5.0.0" p-limit "^3.1.0" +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + jest-circus@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.3.1.tgz#177d07c5c0beae8ef2937a67de68f1e17bbf1b4a" @@ -7165,6 +7460,32 @@ jest-circus@^29.3.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-circus@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.1.tgz#861dab37e71a89907d1c0fabc54a0019738ed824" + integrity sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ== + dependencies: + "@jest/environment" "^29.6.1" + "@jest/expect" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^29.6.1" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-runtime "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" + p-limit "^3.1.0" + pretty-format "^29.6.1" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-cli@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.3.1.tgz#e89dff427db3b1df50cea9a393ebd8640790416d" @@ -7183,6 +7504,24 @@ jest-cli@^29.3.1: prompts "^2.0.1" yargs "^17.3.1" +jest-cli@^29.5.0: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.1.tgz#99d9afa7449538221c71f358f0fdd3e9c6e89f72" + integrity sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing== + dependencies: + "@jest/core" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" + prompts "^2.0.1" + yargs "^17.3.1" + jest-config@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.3.1.tgz#0bc3dcb0959ff8662957f1259947aedaefb7f3c6" @@ -7211,6 +7550,34 @@ jest-config@^29.3.1: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-config@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.1.tgz#d785344509065d53a238224c6cdc0ed8e2f2f0dd" + integrity sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.6.1" + "@jest/types" "^29.6.1" + babel-jest "^29.6.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.6.1" + jest-environment-node "^29.6.1" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.1" + jest-runner "^29.6.1" + jest-util "^29.6.1" + jest-validate "^29.6.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.6.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + jest-diff@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.3.1.tgz#d8215b72fed8f1e647aed2cae6c752a89e757527" @@ -7221,6 +7588,16 @@ jest-diff@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" +jest-diff@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.1.tgz#13df6db0a89ee6ad93c747c75c85c70ba941e545" + integrity sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.6.1" + jest-docblock@^29.2.0: version "29.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82" @@ -7228,6 +7605,13 @@ jest-docblock@^29.2.0: dependencies: detect-newline "^3.0.0" +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== + dependencies: + detect-newline "^3.0.0" + jest-each@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.3.1.tgz#bc375c8734f1bb96625d83d1ca03ef508379e132" @@ -7239,6 +7623,17 @@ jest-each@^29.3.1: jest-util "^29.3.1" pretty-format "^29.3.1" +jest-each@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.1.tgz#975058e5b8f55c6780beab8b6ab214921815c89c" + integrity sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ== + dependencies: + "@jest/types" "^29.6.1" + chalk "^4.0.0" + jest-get-type "^29.4.3" + jest-util "^29.6.1" + pretty-format "^29.6.1" + jest-environment-node@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.3.1.tgz#5023b32472b3fba91db5c799a0d5624ad4803e74" @@ -7251,11 +7646,28 @@ jest-environment-node@^29.3.1: jest-mock "^29.3.1" jest-util "^29.3.1" +jest-environment-node@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.1.tgz#08a122dece39e58bc388da815a2166c58b4abec6" + integrity sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ== + dependencies: + "@jest/environment" "^29.6.1" + "@jest/fake-timers" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + jest-mock "^29.6.1" + jest-util "^29.6.1" + jest-get-type@^29.2.0: version "29.2.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== + jest-haste-map@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.3.1.tgz#af83b4347f1dae5ee8c2fb57368dc0bb3e5af843" @@ -7275,6 +7687,25 @@ jest-haste-map@^29.3.1: optionalDependencies: fsevents "^2.3.2" +jest-haste-map@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.1.tgz#62655c7a1c1b349a3206441330fb2dbdb4b63803" + integrity sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig== + dependencies: + "@jest/types" "^29.6.1" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.4.3" + jest-util "^29.6.1" + jest-worker "^29.6.1" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + jest-jasmine2@29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-29.3.1.tgz#15544cce47a3ad7de1beb6ae9b644d091c0092cb" @@ -7306,6 +7737,14 @@ jest-leak-detector@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" +jest-leak-detector@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz#66a902c81318e66e694df7d096a95466cb962f8e" + integrity sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ== + dependencies: + jest-get-type "^29.4.3" + pretty-format "^29.6.1" + jest-matcher-utils@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz#6e7f53512f80e817dfa148672bd2d5d04914a572" @@ -7316,6 +7755,16 @@ jest-matcher-utils@^29.3.1: jest-get-type "^29.2.0" pretty-format "^29.3.1" +jest-matcher-utils@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz#6c60075d84655d6300c5d5128f46531848160b53" + integrity sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA== + dependencies: + chalk "^4.0.0" + jest-diff "^29.6.1" + jest-get-type "^29.4.3" + pretty-format "^29.6.1" + jest-message-util@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.3.1.tgz#37bc5c468dfe5120712053dd03faf0f053bd6adb" @@ -7331,6 +7780,21 @@ jest-message-util@^29.3.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.1.tgz#d0b21d87f117e1b9e165e24f245befd2ff34ff8d" + integrity sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.6.1" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.3.1.tgz#60287d92e5010979d01f218c6b215b688e0f313e" @@ -7340,6 +7804,15 @@ jest-mock@^29.3.1: "@types/node" "*" jest-util "^29.3.1" +jest-mock@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.1.tgz#049ee26aea8cbf54c764af649070910607316517" + integrity sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + jest-util "^29.6.1" + jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" @@ -7350,6 +7823,11 @@ jest-regex-util@^29.2.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== + jest-resolve-dependencies@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz#a6a329708a128e68d67c49f38678a4a4a914c3bf" @@ -7358,6 +7836,14 @@ jest-resolve-dependencies@^29.3.1: jest-regex-util "^29.2.0" jest-snapshot "^29.3.1" +jest-resolve-dependencies@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz#b85b06670f987a62515bbf625d54a499e3d708f5" + integrity sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw== + dependencies: + jest-regex-util "^29.4.3" + jest-snapshot "^29.6.1" + jest-resolve@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.3.1.tgz#9a4b6b65387a3141e4a40815535c7f196f1a68a7" @@ -7373,6 +7859,21 @@ jest-resolve@^29.3.1: resolve.exports "^1.1.0" slash "^3.0.0" +jest-resolve@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.1.tgz#4c3324b993a85e300add2f8609f51b80ddea39ee" + integrity sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.1" + jest-pnp-resolver "^1.2.2" + jest-util "^29.6.1" + jest-validate "^29.6.1" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + jest-runner@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.3.1.tgz#a92a879a47dd096fea46bb1517b0a99418ee9e2d" @@ -7400,6 +7901,33 @@ jest-runner@^29.3.1: p-limit "^3.1.0" source-map-support "0.5.13" +jest-runner@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.1.tgz#54557087e7972d345540d622ab5bfc3d8f34688c" + integrity sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ== + dependencies: + "@jest/console" "^29.6.1" + "@jest/environment" "^29.6.1" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.4.3" + jest-environment-node "^29.6.1" + jest-haste-map "^29.6.1" + jest-leak-detector "^29.6.1" + jest-message-util "^29.6.1" + jest-resolve "^29.6.1" + jest-runtime "^29.6.1" + jest-util "^29.6.1" + jest-watcher "^29.6.1" + jest-worker "^29.6.1" + p-limit "^3.1.0" + source-map-support "0.5.13" + jest-runtime@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.3.1.tgz#21efccb1a66911d6d8591276a6182f520b86737a" @@ -7428,6 +7956,34 @@ jest-runtime@^29.3.1: slash "^3.0.0" strip-bom "^4.0.0" +jest-runtime@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.1.tgz#8a0fc9274ef277f3d70ba19d238e64334958a0dc" + integrity sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ== + dependencies: + "@jest/environment" "^29.6.1" + "@jest/fake-timers" "^29.6.1" + "@jest/globals" "^29.6.1" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.6.1" + jest-message-util "^29.6.1" + jest-mock "^29.6.1" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.1" + jest-snapshot "^29.6.1" + jest-util "^29.6.1" + slash "^3.0.0" + strip-bom "^4.0.0" + jest-snapshot@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.3.1.tgz#17bcef71a453adc059a18a32ccbd594b8cc4e45e" @@ -7458,6 +8014,33 @@ jest-snapshot@^29.3.1: pretty-format "^29.3.1" semver "^7.3.5" +jest-snapshot@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.1.tgz#0d083cb7de716d5d5cdbe80d598ed2fbafac0239" + integrity sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.6.1" + "@jest/transform" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.6.1" + graceful-fs "^4.2.9" + jest-diff "^29.6.1" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.1" + jest-message-util "^29.6.1" + jest-util "^29.6.1" + natural-compare "^1.4.0" + pretty-format "^29.6.1" + semver "^7.5.3" + jest-util@^29.0.0, jest-util@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.3.1.tgz#1dda51e378bbcb7e3bc9d8ab651445591ed373e1" @@ -7470,6 +8053,18 @@ jest-util@^29.0.0, jest-util@^29.3.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-util@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.1.tgz#c9e29a87a6edbf1e39e6dee2b4689b8a146679cb" + integrity sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg== + dependencies: + "@jest/types" "^29.6.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.3.1.tgz#d56fefaa2e7d1fde3ecdc973c7f7f8f25eea704a" @@ -7482,6 +8077,18 @@ jest-validate@^29.3.1: leven "^3.1.0" pretty-format "^29.3.1" +jest-validate@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.1.tgz#765e684af6e2c86dce950aebefbbcd4546d69f7b" + integrity sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA== + dependencies: + "@jest/types" "^29.6.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.4.3" + leven "^3.1.0" + pretty-format "^29.6.1" + jest-watcher@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.3.1.tgz#3341547e14fe3c0f79f9c3a4c62dbc3fc977fd4a" @@ -7496,6 +8103,20 @@ jest-watcher@^29.3.1: jest-util "^29.3.1" string-length "^4.0.1" +jest-watcher@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.1.tgz#7c0c43ddd52418af134c551c92c9ea31e5ec942e" + integrity sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA== + dependencies: + "@jest/test-result" "^29.6.1" + "@jest/types" "^29.6.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.6.1" + string-length "^4.0.1" + jest-worker@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.3.1.tgz#e9462161017a9bb176380d721cab022661da3d6b" @@ -7506,6 +8127,16 @@ jest-worker@^29.3.1: merge-stream "^2.0.0" supports-color "^8.0.0" +jest-worker@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.1.tgz#64b015f0e985ef3a8ad049b61fe92b3db74a5319" + integrity sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA== + dependencies: + "@types/node" "*" + jest-util "^29.6.1" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jest@29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/jest/-/jest-29.3.1.tgz#c130c0d551ae6b5459b8963747fed392ddbde122" @@ -7516,6 +8147,16 @@ jest@29.3.1: import-local "^3.0.2" jest-cli "^29.3.1" +jest@29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" + integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== + dependencies: + "@jest/core" "^29.5.0" + "@jest/types" "^29.5.0" + import-local "^3.0.2" + jest-cli "^29.5.0" + jmespath@0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" @@ -9294,6 +9935,15 @@ pretty-format@^29.0.0, pretty-format@^29.3.1: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.6.1: + version "29.6.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.1.tgz#ec838c288850b7c4f9090b867c2d4f4edbfb0f3e" + integrity sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog== + dependencies: + "@jest/schemas" "^29.6.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + proc-log@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-1.0.0.tgz#0d927307401f69ed79341e83a0b2c9a13395eb77" @@ -9416,6 +10066,11 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pure-rand@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== + q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -9660,6 +10315,11 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + resolve@^1.1.6, resolve@^1.10.0, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -11027,7 +11687,7 @@ write-file-atomic@^4.0.0: imurmurhash "^0.1.4" signal-exit "^3.0.7" -write-file-atomic@^4.0.1: +write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== From 8c9fd23296eb12b6ec189e3d662eb0c48f96284c Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Fri, 14 Jul 2023 15:52:01 -0700 Subject: [PATCH 31/37] Allow Router and Server to be created without a node object (#4070) --- ironfish/src/node.ts | 2 +- .../rpc/routes/chain/broadcastTransaction.ts | 5 +++- .../src/rpc/routes/chain/estimateFeeRate.ts | 5 +++- .../src/rpc/routes/chain/estimateFeeRates.ts | 5 +++- .../src/rpc/routes/chain/exportChainStream.ts | 3 ++- .../src/rpc/routes/chain/followChainStream.ts | 3 ++- ironfish/src/rpc/routes/chain/getAsset.ts | 5 +++- ironfish/src/rpc/routes/chain/getBlock.ts | 5 +++- ironfish/src/rpc/routes/chain/getChainInfo.ts | 4 ++- .../routes/chain/getConsensusParameters.ts | 3 ++- .../src/rpc/routes/chain/getDifficulty.ts | 5 +++- .../rpc/routes/chain/getNetworkHashPower.ts | 5 +++- .../src/rpc/routes/chain/getNetworkInfo.ts | 5 +++- .../src/rpc/routes/chain/getNoteWitness.ts | 3 ++- .../src/rpc/routes/chain/getTransaction.ts | 5 +++- .../rpc/routes/chain/getTransactionStream.ts | 3 ++- ironfish/src/rpc/routes/chain/showChain.ts | 5 +++- ironfish/src/rpc/routes/config/getConfig.ts | 5 +++- ironfish/src/rpc/routes/config/setConfig.ts | 5 +++- ironfish/src/rpc/routes/config/unsetConfig.ts | 5 +++- .../src/rpc/routes/config/uploadConfig.ts | 5 +++- ironfish/src/rpc/routes/event/onGossip.ts | 5 +++- .../src/rpc/routes/event/onReorganizeChain.ts | 5 +++- .../rpc/routes/event/onTransactionGossip.ts | 5 +++- ironfish/src/rpc/routes/faucet/getFunds.ts | 3 ++- ironfish/src/rpc/routes/mempool/getStatus.ts | 5 +++- .../src/rpc/routes/mempool/getTransactions.ts | 5 +++- .../rpc/routes/miner/blockTemplateStream.ts | 5 +++- ironfish/src/rpc/routes/miner/submitBlock.ts | 5 +++- ironfish/src/rpc/routes/node/getLogStream.ts | 5 +++- ironfish/src/rpc/routes/node/getStatus.ts | 5 +++- ironfish/src/rpc/routes/node/stopNode.ts | 5 +++- ironfish/src/rpc/routes/peer/addPeer.ts | 5 +++- .../src/rpc/routes/peer/getBannedPeers.ts | 5 +++- ironfish/src/rpc/routes/peer/getPeer.ts | 5 +++- .../src/rpc/routes/peer/getPeerMessages.ts | 5 +++- ironfish/src/rpc/routes/peer/getPeers.ts | 5 +++- ironfish/src/rpc/routes/router.ts | 10 ++++--- ironfish/src/rpc/routes/rpc/getStatus.ts | 5 +++- .../src/rpc/routes/wallet/addTransaction.ts | 5 +++- ironfish/src/rpc/routes/wallet/burnAsset.ts | 3 ++- ironfish/src/rpc/routes/wallet/create.ts | 5 +++- .../rpc/routes/wallet/createTransaction.ts | 3 ++- .../src/rpc/routes/wallet/exportAccount.ts | 5 +++- .../routes/wallet/getAccountNotesStream.ts | 5 +++- .../routes/wallet/getAccountTransaction.ts | 5 +++- .../routes/wallet/getAccountTransactions.ts | 5 +++- ironfish/src/rpc/routes/wallet/getAccounts.ts | 5 +++- .../rpc/routes/wallet/getAccountsStatus.ts | 5 +++- ironfish/src/rpc/routes/wallet/getAssets.ts | 5 +++- ironfish/src/rpc/routes/wallet/getBalance.ts | 5 +++- ironfish/src/rpc/routes/wallet/getBalances.ts | 5 +++- .../rpc/routes/wallet/getDefaultAccount.ts | 5 +++- ironfish/src/rpc/routes/wallet/getNotes.ts | 5 +++- .../src/rpc/routes/wallet/getPublicKey.ts | 5 +++- .../src/rpc/routes/wallet/importAccount.ts | 5 +++- ironfish/src/rpc/routes/wallet/mintAsset.ts | 3 ++- .../src/rpc/routes/wallet/postTransaction.ts | 5 +++- ironfish/src/rpc/routes/wallet/remove.ts | 5 +++- ironfish/src/rpc/routes/wallet/rename.ts | 5 +++- .../src/rpc/routes/wallet/rescanAccount.ts | 5 +++- .../src/rpc/routes/wallet/sendTransaction.ts | 5 +++- ironfish/src/rpc/routes/wallet/use.ts | 5 +++- ironfish/src/rpc/routes/worker/getStatus.ts | 5 +++- ironfish/src/rpc/server.ts | 26 ++++++++++++------- 65 files changed, 252 insertions(+), 77 deletions(-) diff --git a/ironfish/src/node.ts b/ironfish/src/node.ts index a97f3ad30b..179e76911b 100644 --- a/ironfish/src/node.ts +++ b/ironfish/src/node.ts @@ -103,7 +103,7 @@ export class IronfishNode { this.miningManager = new MiningManager({ chain, memPool, node: this, metrics }) this.memPool = memPool this.workerPool = workerPool - this.rpc = new RpcServer(this) + this.rpc = new RpcServer({ node: this, wallet }, internal) this.logger = logger this.pkg = pkg diff --git a/ironfish/src/rpc/routes/chain/broadcastTransaction.ts b/ironfish/src/rpc/routes/chain/broadcastTransaction.ts index 8fbf09a780..09832d60cf 100644 --- a/ironfish/src/rpc/routes/chain/broadcastTransaction.ts +++ b/ironfish/src/rpc/routes/chain/broadcastTransaction.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' +import { Assert } from '../../../assert' import { Transaction } from '../../../primitives' import { ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' @@ -32,7 +33,9 @@ export const BroadcastTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/broadcastTransaction`, BroadcastTransactionRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const data = Buffer.from(request.data.transaction, 'hex') const transaction = new Transaction(data) diff --git a/ironfish/src/rpc/routes/chain/estimateFeeRate.ts b/ironfish/src/rpc/routes/chain/estimateFeeRate.ts index 590c40a28a..cf0dff184e 100644 --- a/ironfish/src/rpc/routes/chain/estimateFeeRate.ts +++ b/ironfish/src/rpc/routes/chain/estimateFeeRate.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { PRIORITY_LEVELS, PriorityLevel } from '../../../memPool/feeEstimator' import { CurrencyUtils } from '../../../utils' import { ApiNamespace, router } from '../router' @@ -27,7 +28,9 @@ export const EstimateFeeRateResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/estimateFeeRate`, EstimateFeeRateRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const priority = request.data?.priority ?? 'average' const rate = node.memPool.feeEstimator.estimateFeeRate(priority) request.end({ rate: CurrencyUtils.encode(rate) }) diff --git a/ironfish/src/rpc/routes/chain/estimateFeeRates.ts b/ironfish/src/rpc/routes/chain/estimateFeeRates.ts index 0d3a350bec..fa9a79ed0c 100644 --- a/ironfish/src/rpc/routes/chain/estimateFeeRates.ts +++ b/ironfish/src/rpc/routes/chain/estimateFeeRates.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { CurrencyUtils } from '../../../utils' import { ApiNamespace, router } from '../router' @@ -27,7 +28,9 @@ export const EstimateFeeRatesResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/estimateFeeRates`, EstimateFeeRatesRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const rates = node.memPool.feeEstimator.estimateFeeRates() request.end({ diff --git a/ironfish/src/rpc/routes/chain/exportChainStream.ts b/ironfish/src/rpc/routes/chain/exportChainStream.ts index 684c22644d..dade9539d8 100644 --- a/ironfish/src/rpc/routes/chain/exportChainStream.ts +++ b/ironfish/src/rpc/routes/chain/exportChainStream.ts @@ -61,7 +61,8 @@ export const ExportChainStreamResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/exportChainStream`, ExportChainStreamRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) Assert.isNotNull(node.chain.head, 'head') Assert.isNotNull(node.chain.latest, 'latest') diff --git a/ironfish/src/rpc/routes/chain/followChainStream.ts b/ironfish/src/rpc/routes/chain/followChainStream.ts index fe361e69da..0b9756a99b 100644 --- a/ironfish/src/rpc/routes/chain/followChainStream.ts +++ b/ironfish/src/rpc/routes/chain/followChainStream.ts @@ -139,7 +139,8 @@ export const FollowChainStreamResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/followChainStream`, FollowChainStreamRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) const head = request.data?.head ? Buffer.from(request.data.head, 'hex') : null const processor = new ChainProcessor({ diff --git a/ironfish/src/rpc/routes/chain/getAsset.ts b/ironfish/src/rpc/routes/chain/getAsset.ts index 8f5caa52f5..bc5d1262b4 100644 --- a/ironfish/src/rpc/routes/chain/getAsset.ts +++ b/ironfish/src/rpc/routes/chain/getAsset.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { ASSET_ID_LENGTH } from '@ironfish/rust-nodejs' import * as yup from 'yup' +import { Assert } from '../../../assert' import { CurrencyUtils } from '../../../utils' import { ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' @@ -41,7 +42,9 @@ export const GetAssetResponse: yup.ObjectSchema = yup router.register( `${ApiNamespace.chain}/getAsset`, GetAssetRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const id = Buffer.from(request.data.id, 'hex') if (id.byteLength !== ASSET_ID_LENGTH) { diff --git a/ironfish/src/rpc/routes/chain/getBlock.ts b/ironfish/src/rpc/routes/chain/getBlock.ts index a5154d3e9e..8fb7c881a1 100644 --- a/ironfish/src/rpc/routes/chain/getBlock.ts +++ b/ironfish/src/rpc/routes/chain/getBlock.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { BlockHeader } from '../../../primitives' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives/block' import { BufferUtils } from '../../../utils' @@ -88,7 +89,9 @@ export const GetBlockResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.chain}/getBlock`, GetBlockRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + let header: BlockHeader | null = null let error = '' diff --git a/ironfish/src/rpc/routes/chain/getChainInfo.ts b/ironfish/src/rpc/routes/chain/getChainInfo.ts index 5622e2a3ed..f7e70d67ca 100644 --- a/ironfish/src/rpc/routes/chain/getChainInfo.ts +++ b/ironfish/src/rpc/routes/chain/getChainInfo.ts @@ -44,7 +44,9 @@ export const GetChainInfoResponseSchema: yup.ObjectSchema router.register( `${ApiNamespace.chain}/getChainInfo`, GetChainInfoRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + Assert.isNotNull(node.chain.genesis, 'no genesis') const latestHeader = node.chain.latest diff --git a/ironfish/src/rpc/routes/chain/getConsensusParameters.ts b/ironfish/src/rpc/routes/chain/getConsensusParameters.ts index 6261b6d255..9b92816e74 100644 --- a/ironfish/src/rpc/routes/chain/getConsensusParameters.ts +++ b/ironfish/src/rpc/routes/chain/getConsensusParameters.ts @@ -35,7 +35,8 @@ export const GetConsensusParametersResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getConsensusParameters`, GetConsensusParametersRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) Assert.isNotNull(node.chain.consensus, 'no consensus parameters') const consensusParameters = node.chain.consensus.parameters diff --git a/ironfish/src/rpc/routes/chain/getDifficulty.ts b/ironfish/src/rpc/routes/chain/getDifficulty.ts index bfaeb6e1d0..54eab85934 100644 --- a/ironfish/src/rpc/routes/chain/getDifficulty.ts +++ b/ironfish/src/rpc/routes/chain/getDifficulty.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' @@ -34,7 +35,9 @@ export const GetDifficultyResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getDifficulty`, GetDifficultyRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + let sequence = node.chain.head.sequence let block = node.chain.head diff --git a/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts b/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts index c879ee3154..b504a7e51a 100644 --- a/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts +++ b/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { BigIntUtils } from '../../../utils' import { ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' @@ -37,7 +38,9 @@ export const GetNetworkHashPowerResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getNetworkHashPower`, GetNetworkHashPowerRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + let blocks = request.data?.blocks ?? 120 let sequence = request.data?.sequence ?? -1 diff --git a/ironfish/src/rpc/routes/chain/getNetworkInfo.ts b/ironfish/src/rpc/routes/chain/getNetworkInfo.ts index 524d187c06..43a235dc3b 100644 --- a/ironfish/src/rpc/routes/chain/getNetworkInfo.ts +++ b/ironfish/src/rpc/routes/chain/getNetworkInfo.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' export type GetNetworkInfoRequest = undefined @@ -22,7 +23,9 @@ export const GetNetworkInfoResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getNetworkInfo`, GetNetworkInfoRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + request.end({ networkId: node.internal.get('networkId'), }) diff --git a/ironfish/src/rpc/routes/chain/getNoteWitness.ts b/ironfish/src/rpc/routes/chain/getNoteWitness.ts index 1da774b188..31a538e3a3 100644 --- a/ironfish/src/rpc/routes/chain/getNoteWitness.ts +++ b/ironfish/src/rpc/routes/chain/getNoteWitness.ts @@ -48,7 +48,8 @@ export const GetNoteWitnessResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getNoteWitness`, GetNoteWitnessRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) const { chain } = node const confirmations = request.data.confirmations ?? node.config.get('confirmations') diff --git a/ironfish/src/rpc/routes/chain/getTransaction.ts b/ironfish/src/rpc/routes/chain/getTransaction.ts index 540999d118..1ba924ae31 100644 --- a/ironfish/src/rpc/routes/chain/getTransaction.ts +++ b/ironfish/src/rpc/routes/chain/getTransaction.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { BlockHashSerdeInstance } from '../../../serde' import { CurrencyUtils } from '../../../utils' import { NotFoundError, ValidationError } from '../../adapters' @@ -80,7 +81,9 @@ export const GetTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getTransaction`, GetTransactionRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + if (!request.data.transactionHash) { throw new ValidationError(`Missing transaction hash`) } diff --git a/ironfish/src/rpc/routes/chain/getTransactionStream.ts b/ironfish/src/rpc/routes/chain/getTransactionStream.ts index 401bf8761b..94f82de330 100644 --- a/ironfish/src/rpc/routes/chain/getTransactionStream.ts +++ b/ironfish/src/rpc/routes/chain/getTransactionStream.ts @@ -125,7 +125,8 @@ export const GetTransactionStreamResponseSchema: yup.ObjectSchema( `${ApiNamespace.chain}/getTransactionStream`, GetTransactionStreamRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) if (!isValidIncomingViewKey(request.data.incomingViewKey)) { throw new ValidationError(`incomingViewKey is not valid`) } diff --git a/ironfish/src/rpc/routes/chain/showChain.ts b/ironfish/src/rpc/routes/chain/showChain.ts index fc0cc87ddb..e86b26fa3c 100644 --- a/ironfish/src/rpc/routes/chain/showChain.ts +++ b/ironfish/src/rpc/routes/chain/showChain.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' import { renderChain } from './utils' @@ -35,7 +36,9 @@ export const ShowChainResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.chain}/showChain`, ShowChainRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const content = await renderChain(node.chain, request.data?.start, request.data?.stop, { indent: ' ', work: false, diff --git a/ironfish/src/rpc/routes/config/getConfig.ts b/ironfish/src/rpc/routes/config/getConfig.ts index f5804037a6..55d1d64792 100644 --- a/ironfish/src/rpc/routes/config/getConfig.ts +++ b/ironfish/src/rpc/routes/config/getConfig.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' import { ValidationError } from '../../adapters/errors' import { ApiNamespace, router } from '../router' @@ -21,7 +22,9 @@ export const GetConfigResponseSchema: yup.ObjectSchema = Conf router.register( `${ApiNamespace.config}/getConfig`, GetConfigRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + if (request.data?.name && !(request.data.name in node.config.defaults)) { throw new ValidationError(`No config option ${String(request.data.name)}`) } diff --git a/ironfish/src/rpc/routes/config/setConfig.ts b/ironfish/src/rpc/routes/config/setConfig.ts index 9bd3caf45a..22728a6c43 100644 --- a/ironfish/src/rpc/routes/config/setConfig.ts +++ b/ironfish/src/rpc/routes/config/setConfig.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' import { ApiNamespace, router } from '../router' import { setUnknownConfigValue } from './uploadConfig' @@ -21,7 +22,9 @@ export const SetConfigResponseSchema: yup.ObjectSchema = Conf router.register( `${ApiNamespace.config}/setConfig`, SetConfigRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + setUnknownConfigValue(node.config, request.data.name, request.data.value) await node.config.save() request.end() diff --git a/ironfish/src/rpc/routes/config/unsetConfig.ts b/ironfish/src/rpc/routes/config/unsetConfig.ts index 8f2ebd2e0c..9946f2d1ba 100644 --- a/ironfish/src/rpc/routes/config/unsetConfig.ts +++ b/ironfish/src/rpc/routes/config/unsetConfig.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' import { ApiNamespace, router } from '../router' import { setUnknownConfigValue } from './uploadConfig' @@ -21,7 +22,9 @@ export const UnsetConfigResponseSchema: yup.ObjectSchema = router.register( `${ApiNamespace.config}/unsetConfig`, UnsetConfigRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + setUnknownConfigValue(node.config, request.data.name) await node.config.save() request.end() diff --git a/ironfish/src/rpc/routes/config/uploadConfig.ts b/ironfish/src/rpc/routes/config/uploadConfig.ts index c076b03833..4dbdd1a744 100644 --- a/ironfish/src/rpc/routes/config/uploadConfig.ts +++ b/ironfish/src/rpc/routes/config/uploadConfig.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Config, ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' import { ValidationError } from '../../adapters/errors' import { ApiNamespace, router } from '../router' @@ -19,7 +20,9 @@ export const UploadConfigResponseSchema: yup.ObjectSchema router.register( `${ApiNamespace.config}/uploadConfig`, UploadConfigRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + clearConfig(node.config) for (const key of Object.keys(request.data.config)) { diff --git a/ironfish/src/rpc/routes/event/onGossip.ts b/ironfish/src/rpc/routes/event/onGossip.ts index 5be4c3e1b5..66c11209f5 100644 --- a/ironfish/src/rpc/routes/event/onGossip.ts +++ b/ironfish/src/rpc/routes/event/onGossip.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { BlockHeader } from '../../../primitives' import { ApiNamespace, router } from '../router' import { RpcBlockHeader, RpcBlockHeaderSchema, serializeRpcBlockHeader } from './types' @@ -21,7 +22,9 @@ export const OnGossipResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.event}/onGossip`, OnGossipRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + function onGossip(header: BlockHeader) { const serialized = serializeRpcBlockHeader(header) request.stream({ blockHeader: serialized }) diff --git a/ironfish/src/rpc/routes/event/onReorganizeChain.ts b/ironfish/src/rpc/routes/event/onReorganizeChain.ts index ad7257ac2a..efc9f09b55 100644 --- a/ironfish/src/rpc/routes/event/onReorganizeChain.ts +++ b/ironfish/src/rpc/routes/event/onReorganizeChain.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { BlockHeader } from '../../../primitives' import { ApiNamespace, router } from '../router' import { RpcBlockHeader, RpcBlockHeaderSchema, serializeRpcBlockHeader } from './types' @@ -27,7 +28,9 @@ export const OnReorganizeChainResponseSchema: yup.ObjectSchema( `${ApiNamespace.event}/onReorganizeChain`, OnReorganizeChainRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + function onReorganizeChain(oldHead: BlockHeader, newHead: BlockHeader, fork: BlockHeader) { request.stream({ oldHead: serializeRpcBlockHeader(oldHead), diff --git a/ironfish/src/rpc/routes/event/onTransactionGossip.ts b/ironfish/src/rpc/routes/event/onTransactionGossip.ts index f324888337..f47bab9daf 100644 --- a/ironfish/src/rpc/routes/event/onTransactionGossip.ts +++ b/ironfish/src/rpc/routes/event/onTransactionGossip.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Transaction } from '../../../primitives' import { ApiNamespace, router } from '../router' @@ -25,7 +26,9 @@ export const OnTransactionGossipResponseSchema: yup.ObjectSchema( `${ApiNamespace.event}/onTransactionGossip`, OnTransactionGossipRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const onTransactionGossip = (transaction: Transaction) => { request.stream({ serializedTransaction: transaction.serialize().toString('hex'), diff --git a/ironfish/src/rpc/routes/faucet/getFunds.ts b/ironfish/src/rpc/routes/faucet/getFunds.ts index 3cd551be82..7f0fcd5650 100644 --- a/ironfish/src/rpc/routes/faucet/getFunds.ts +++ b/ironfish/src/rpc/routes/faucet/getFunds.ts @@ -28,7 +28,8 @@ export const GetFundsResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.faucet}/getFunds`, GetFundsRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) // check node network id const networkId = node.internal.get('networkId') diff --git a/ironfish/src/rpc/routes/mempool/getStatus.ts b/ironfish/src/rpc/routes/mempool/getStatus.ts index 0ee901ca37..d009b106be 100644 --- a/ironfish/src/rpc/routes/mempool/getStatus.ts +++ b/ironfish/src/rpc/routes/mempool/getStatus.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { PromiseUtils } from '../../../utils' import { ApiNamespace, router } from '../router' @@ -50,7 +51,9 @@ export const GetMempoolStatusResponseSchema: yup.ObjectSchema( `${ApiNamespace.mempool}/getStatus`, GetMempoolStatusRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const status = getStatus(node) if (!request.data?.stream) { diff --git a/ironfish/src/rpc/routes/mempool/getTransactions.ts b/ironfish/src/rpc/routes/mempool/getTransactions.ts index c55bd54b1c..754acff7ce 100644 --- a/ironfish/src/rpc/routes/mempool/getTransactions.ts +++ b/ironfish/src/rpc/routes/mempool/getTransactions.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { getFeeRate } from '../../../memPool' import { Transaction } from '../../../primitives' import { ApiNamespace, router } from '../router' @@ -57,7 +58,9 @@ export const MempoolTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.mempool}/getTransactions`, MempoolTransactionsRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + let position = 0 let streamed = 0 diff --git a/ironfish/src/rpc/routes/miner/blockTemplateStream.ts b/ironfish/src/rpc/routes/miner/blockTemplateStream.ts index 9a1ba1f860..f28fcc6f7a 100644 --- a/ironfish/src/rpc/routes/miner/blockTemplateStream.ts +++ b/ironfish/src/rpc/routes/miner/blockTemplateStream.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { SerializedBlockTemplate } from '../../../serde/BlockTemplateSerde' import { ApiNamespace, router } from '../router' @@ -43,7 +44,9 @@ export const BlockTemplateStreamResponseSchema: yup.ObjectSchema( `${ApiNamespace.miner}/blockTemplateStream`, BlockTemplateStreamRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + if (!node.chain.synced && !node.config.get('miningForce')) { node.logger.info( 'Miner connected while the node is syncing. Will not start mining until the node is synced', diff --git a/ironfish/src/rpc/routes/miner/submitBlock.ts b/ironfish/src/rpc/routes/miner/submitBlock.ts index a766cf1022..7a928aacab 100644 --- a/ironfish/src/rpc/routes/miner/submitBlock.ts +++ b/ironfish/src/rpc/routes/miner/submitBlock.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { MINED_RESULT } from '../../../mining/manager' import { SerializedBlockTemplate } from '../../../serde' import { ApiNamespace, router } from '../router' @@ -62,7 +63,9 @@ export const SubmitBlockResponseSchema: yup.ObjectSchema = router.register( `${ApiNamespace.miner}/submitBlock`, SubmitBlockRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const result = await node.miningManager.submitBlockTemplate(request.data) request.end({ diff --git a/ironfish/src/rpc/routes/node/getLogStream.ts b/ironfish/src/rpc/routes/node/getLogStream.ts index f0a4b89179..5a8e5e89e8 100644 --- a/ironfish/src/rpc/routes/node/getLogStream.ts +++ b/ironfish/src/rpc/routes/node/getLogStream.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { ConsolaReporterLogObject } from 'consola' import * as yup from 'yup' +import { Assert } from '../../../assert' import { InterceptReporter } from '../../../logger' import { IJSON } from '../../../serde' import { ApiNamespace, router } from '../router' @@ -36,7 +37,9 @@ export const GetLogStreamResponseSchema: yup.ObjectSchema router.register( `${ApiNamespace.node}/getLogStream`, GetLogStreamRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const reporter = new InterceptReporter((logObj: ConsolaReporterLogObject): void => { request.stream({ level: String(logObj.level), diff --git a/ironfish/src/rpc/routes/node/getStatus.ts b/ironfish/src/rpc/routes/node/getStatus.ts index 76ad7a5f62..142dc1bd49 100644 --- a/ironfish/src/rpc/routes/node/getStatus.ts +++ b/ironfish/src/rpc/routes/node/getStatus.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { getActiveReqs, isActive } from 'libuv-monitor' import * as yup from 'yup' +import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { MathUtils, PromiseUtils } from '../../../utils' import { ApiNamespace, router } from '../router' @@ -254,7 +255,9 @@ export const GetStatusResponseSchema: yup.ObjectSchema = router.register( `${ApiNamespace.node}/getStatus`, GetStatusRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const status = getStatus(node) if (!request.data?.stream) { diff --git a/ironfish/src/rpc/routes/node/stopNode.ts b/ironfish/src/rpc/routes/node/stopNode.ts index d0baf396fb..5ac1ce2cc5 100644 --- a/ironfish/src/rpc/routes/node/stopNode.ts +++ b/ironfish/src/rpc/routes/node/stopNode.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types @@ -19,7 +20,9 @@ export const StopNodeResponseSchema: yup.MixedSchema = yup router.register( `${ApiNamespace.node}/stopNode`, StopNodeRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + node.logger.withTag('stopnode').info('Shutting down') request.end() await node.shutdown() diff --git a/ironfish/src/rpc/routes/peer/addPeer.ts b/ironfish/src/rpc/routes/peer/addPeer.ts index 293e7b5e35..1c5f72f0de 100644 --- a/ironfish/src/rpc/routes/peer/addPeer.ts +++ b/ironfish/src/rpc/routes/peer/addPeer.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { DEFAULT_WEBSOCKET_PORT } from '../../../fileStores/config' import { ApiNamespace, router } from '../router' @@ -33,7 +34,9 @@ export const AddPeerResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.peer}/addPeer`, AddPeerRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const peerManager = node.peerNetwork.peerManager const { host, port, whitelist } = request.data diff --git a/ironfish/src/rpc/routes/peer/getBannedPeers.ts b/ironfish/src/rpc/routes/peer/getBannedPeers.ts index 6fd4d3521e..b8c83c1c12 100644 --- a/ironfish/src/rpc/routes/peer/getBannedPeers.ts +++ b/ironfish/src/rpc/routes/peer/getBannedPeers.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { PeerNetwork } from '../../../network' import { ApiNamespace, router } from '../router' @@ -45,7 +46,9 @@ export const GetBannedPeersResponseSchema: yup.ObjectSchema( `${ApiNamespace.peer}/getBannedPeers`, GetBannedPeersRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const peerNetwork = node.peerNetwork const peers = getPeers(peerNetwork) diff --git a/ironfish/src/rpc/routes/peer/getPeer.ts b/ironfish/src/rpc/routes/peer/getPeer.ts index 4d3fb90fa4..c873d96e6e 100644 --- a/ironfish/src/rpc/routes/peer/getPeer.ts +++ b/ironfish/src/rpc/routes/peer/getPeer.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Connection, PeerNetwork } from '../../../network' import { ApiNamespace, router } from '../router' import { PeerResponse } from './getPeers' @@ -60,7 +61,9 @@ export const GetPeerResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.peer}/getPeer`, GetPeerRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const peerNetwork = node.peerNetwork if (!peerNetwork) { diff --git a/ironfish/src/rpc/routes/peer/getPeerMessages.ts b/ironfish/src/rpc/routes/peer/getPeerMessages.ts index 84a0d3dc46..34256ee6a8 100644 --- a/ironfish/src/rpc/routes/peer/getPeerMessages.ts +++ b/ironfish/src/rpc/routes/peer/getPeerMessages.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Connection, PeerNetwork } from '../../../network' import { NetworkMessageType } from '../../../network/types' import { IJSON } from '../../../serde' @@ -60,7 +61,9 @@ export const GetPeerMessagesResponseSchema: yup.ObjectSchema( `${ApiNamespace.peer}/getPeerMessages`, GetPeerMessagesRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const peerNetwork = node.peerNetwork if (!peerNetwork) { diff --git a/ironfish/src/rpc/routes/peer/getPeers.ts b/ironfish/src/rpc/routes/peer/getPeers.ts index 160427bd18..c8f23d53e4 100644 --- a/ironfish/src/rpc/routes/peer/getPeers.ts +++ b/ironfish/src/rpc/routes/peer/getPeers.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Connection, PeerNetwork } from '../../../network' import { Features } from '../../../network/peers/peerFeatures' import { ApiNamespace, router } from '../router' @@ -87,7 +88,9 @@ export const GetPeersResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.peer}/getPeers`, GetPeersRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const peerNetwork = node.peerNetwork if (!peerNetwork) { diff --git a/ironfish/src/rpc/routes/router.ts b/ironfish/src/rpc/routes/router.ts index 9d1bb1144f..6cc39fbd7d 100644 --- a/ironfish/src/rpc/routes/router.ts +++ b/ironfish/src/rpc/routes/router.ts @@ -5,6 +5,7 @@ import { Assert } from '../../assert' import { IronfishNode } from '../../node' import { YupSchema, YupSchemaResult, YupUtils } from '../../utils' import { StrEnumUtils } from '../../utils/enums' +import { Wallet } from '../../wallet' import { ERROR_CODES } from '../adapters' import { ResponseError, ValidationError } from '../adapters/errors' import { RpcRequest } from '../request' @@ -26,9 +27,11 @@ export enum ApiNamespace { export const ALL_API_NAMESPACES = StrEnumUtils.getValues(ApiNamespace) +export type RequestContext = { node?: IronfishNode; wallet?: Wallet } + export type RouteHandler = ( request: RpcRequest, - node: IronfishNode, + context: RequestContext, ) => Promise | void export class RouteNotFoundError extends ResponseError { @@ -50,6 +53,7 @@ export function parseRoute( export class Router { routes = new Map>() + context: RequestContext = {} server: RpcServer | null = null register( @@ -96,10 +100,8 @@ export class Router { } request.data = result - Assert.isNotNull(this.server) - try { - await handler(request, this.server.node) + await handler(request, this.context) } catch (e: unknown) { if (e instanceof ResponseError) { throw e diff --git a/ironfish/src/rpc/routes/rpc/getStatus.ts b/ironfish/src/rpc/routes/rpc/getStatus.ts index 86e1e1cb46..5930f29fe7 100644 --- a/ironfish/src/rpc/routes/rpc/getStatus.ts +++ b/ironfish/src/rpc/routes/rpc/getStatus.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { PromiseUtils } from '../../../utils' import { RpcHttpAdapter, RpcIpcAdapter } from '../../adapters' @@ -62,7 +63,9 @@ export const GetRpcStatusResponseSchema: yup.ObjectSchema router.register( `${ApiNamespace.rpc}/getStatus`, GetRpcStatusRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const jobs = await getRpcStatus(node) if (!request.data?.stream) { diff --git a/ironfish/src/rpc/routes/wallet/addTransaction.ts b/ironfish/src/rpc/routes/wallet/addTransaction.ts index a9ea7d052e..9d12231e64 100644 --- a/ironfish/src/rpc/routes/wallet/addTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/addTransaction.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Transaction } from '../../../primitives' import { AsyncUtils } from '../../../utils' import { ValidationError } from '../../adapters' @@ -36,7 +37,9 @@ export const AddTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/addTransaction`, AddTransactionRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const data = Buffer.from(request.data.transaction, 'hex') const transaction = new Transaction(data) diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.ts b/ironfish/src/rpc/routes/wallet/burnAsset.ts index 5070b39581..3da10513d5 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.ts @@ -48,7 +48,8 @@ export const BurnAssetResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.wallet}/burnAsset`, BurnAssetRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) const account = getAccount(node.wallet, request.data.account) const fee = CurrencyUtils.decode(request.data.fee) diff --git a/ironfish/src/rpc/routes/wallet/create.ts b/ironfish/src/rpc/routes/wallet/create.ts index 6e69b8f5d2..a0a933ff07 100644 --- a/ironfish/src/rpc/routes/wallet/create.ts +++ b/ironfish/src/rpc/routes/wallet/create.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ERROR_CODES, ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' @@ -30,7 +31,9 @@ export const CreateAccountResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/create`, CreateAccountRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const name = request.data.name if (node.wallet.accountExists(name)) { diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index 6ef8b223b6..a638a50954 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -102,7 +102,8 @@ export const CreateTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/createTransaction`, CreateTransactionRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) const account = getAccount(node.wallet, request.data.account) const params: Parameters[0] = { diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.ts b/ironfish/src/rpc/routes/wallet/exportAccount.ts index d3132689c7..d1d96b0e94 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { LanguageKey, LanguageUtils } from '../../../utils' import { encodeAccount } from '../../../wallet/account/encoder/account' import { AccountFormat } from '../../../wallet/account/encoder/encoder' @@ -37,7 +38,9 @@ export const ExportAccountResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/exportAccount`, ExportAccountRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) const { id: _, ...accountInfo } = account.serialize() if (request.data.viewOnly) { diff --git a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts index bbb6f88074..1c0d5ad5ab 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { CurrencyUtils } from '../../../utils' import { ApiNamespace, router } from '../router' import { RpcWalletNote, RpcWalletNoteSchema } from './types' @@ -24,7 +25,9 @@ export const GetAccountNotesStreamResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/getAccountNotesStream`, GetAccountNotesStreamRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) for await (const transaction of account.getTransactionsByTime()) { diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts index f1dda046fd..a52552a3cd 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { TransactionStatus, TransactionType } from '../../../wallet' import { ApiNamespace, router } from '../router' import { RpcSpend, RpcSpendSchema, RpcWalletNote, RpcWalletNoteSchema } from './types' @@ -89,7 +90,9 @@ export const GetAccountTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/getAccountTransaction`, GetAccountTransactionRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) const transactionHash = Buffer.from(request.data.hash, 'hex') diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts b/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts index fc567ddf09..6e4f8ce632 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives' import { TransactionStatus, TransactionType } from '../../../wallet' @@ -98,7 +99,9 @@ export const GetAccountTransactionsResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/getAccountTransactions`, GetAccountTransactionsRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) const headSequence = (await account.getHead())?.sequence ?? null diff --git a/ironfish/src/rpc/routes/wallet/getAccounts.ts b/ironfish/src/rpc/routes/wallet/getAccounts.ts index 45265fbeed..ffa3277baf 100644 --- a/ironfish/src/rpc/routes/wallet/getAccounts.ts +++ b/ironfish/src/rpc/routes/wallet/getAccounts.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { Account } from '../../../wallet' import { ApiNamespace, router } from '../router' @@ -25,7 +26,9 @@ export const GetAccountsResponseSchema: yup.ObjectSchema = router.register( `${ApiNamespace.wallet}/getAccounts`, GetAccountsRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + let accounts: Account[] = [] if (request.data?.default) { diff --git a/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts b/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts index 7df09e9f76..7e7a7cac16 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' export type GetAccountStatusRequest = { account?: string } @@ -41,7 +42,9 @@ export const GetAccountStatusResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/getAccountsStatus`, GetAccountStatusRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const headHashes = new Map() for await (const { accountId, head } of node.wallet.walletDb.loadHeads()) { headHashes.set(accountId, head?.hash ?? null) diff --git a/ironfish/src/rpc/routes/wallet/getAssets.ts b/ironfish/src/rpc/routes/wallet/getAssets.ts index 0c9b4e47ef..506618c1ec 100644 --- a/ironfish/src/rpc/routes/wallet/getAssets.ts +++ b/ironfish/src/rpc/routes/wallet/getAssets.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { AssetVerification } from '../../../assets' import { CurrencyUtils } from '../../../utils' import { ApiNamespace, router } from '../router' @@ -49,7 +50,9 @@ export const GetAssetsResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.wallet}/getAssets`, GetAssetsRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) for await (const asset of account.getAssets()) { diff --git a/ironfish/src/rpc/routes/wallet/getBalance.ts b/ironfish/src/rpc/routes/wallet/getBalance.ts index f79b42a66d..8bfdb00d57 100644 --- a/ironfish/src/rpc/routes/wallet/getBalance.ts +++ b/ironfish/src/rpc/routes/wallet/getBalance.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Asset } from '@ironfish/rust-nodejs' import * as yup from 'yup' +import { Assert } from '../../../assert' import { AssetVerification } from '../../../assets' import { ApiNamespace, router } from '../router' import { getAccount } from './utils' @@ -60,7 +61,9 @@ export const GetBalanceResponseSchema: yup.ObjectSchema = yu router.register( `${ApiNamespace.wallet}/getBalance`, GetBalanceRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const confirmations = request.data?.confirmations ?? node.config.get('confirmations') const account = getAccount(node.wallet, request.data?.account) diff --git a/ironfish/src/rpc/routes/wallet/getBalances.ts b/ironfish/src/rpc/routes/wallet/getBalances.ts index 094f97e7eb..22083a9cf7 100644 --- a/ironfish/src/rpc/routes/wallet/getBalances.ts +++ b/ironfish/src/rpc/routes/wallet/getBalances.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { AssetVerification } from '../../../assets' import { CurrencyUtils } from '../../../utils' import { ApiNamespace, router } from '../router' @@ -72,7 +73,9 @@ export const GetBalancesResponseSchema: yup.ObjectSchema = router.register( `${ApiNamespace.wallet}/getBalances`, GetBalancesRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) const balances = [] diff --git a/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts b/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts index 38d7008c50..f7d0b9c715 100644 --- a/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts +++ b/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types @@ -27,7 +28,9 @@ export const GetDefaultAccountResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/getDefaultAccount`, GetDefaultAccountRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const account = node.wallet.getDefaultAccount() request.end({ account: account ? { name: account.name } : null }) }, diff --git a/ironfish/src/rpc/routes/wallet/getNotes.ts b/ironfish/src/rpc/routes/wallet/getNotes.ts index 95c9a5a42f..4cc30e301d 100644 --- a/ironfish/src/rpc/routes/wallet/getNotes.ts +++ b/ironfish/src/rpc/routes/wallet/getNotes.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' import { RpcWalletNote, RpcWalletNoteSchema } from './types' import { getAccount, serializeRpcWalletNote } from './utils' @@ -71,7 +72,9 @@ export const GetNotesResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.wallet}/getNotes`, GetNotesRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) const pageSize = request.data.pageSize ?? DEFAULT_PAGE_SIZE const pageCursor = request.data.pageCursor diff --git a/ironfish/src/rpc/routes/wallet/getPublicKey.ts b/ironfish/src/rpc/routes/wallet/getPublicKey.ts index 10f88ba4b2..6fa9d67d17 100644 --- a/ironfish/src/rpc/routes/wallet/getPublicKey.ts +++ b/ironfish/src/rpc/routes/wallet/getPublicKey.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' import { getAccount } from './utils' @@ -24,7 +25,9 @@ export const GetPublicKeyResponseSchema: yup.ObjectSchema router.register( `${ApiNamespace.wallet}/getPublicKey`, GetPublicKeyRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) request.end({ diff --git a/ironfish/src/rpc/routes/wallet/importAccount.ts b/ironfish/src/rpc/routes/wallet/importAccount.ts index 7b3191e5a7..a6c65e33bf 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { v4 as uuid } from 'uuid' import * as yup from 'yup' +import { Assert } from '../../../assert' import { decodeAccount } from '../../../wallet/account/encoder/account' import { ApiNamespace, router } from '../router' import { RpcAccountImport } from './types' @@ -39,7 +40,9 @@ export const ImportAccountResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.wallet}/importAccount`, ImportAccountRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + let accountImport = null if (typeof request.data.account === 'string') { accountImport = decodeAccount(request.data.account, { diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.ts b/ironfish/src/rpc/routes/wallet/mintAsset.ts index 4b2c032465..4e5f327869 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.ts @@ -54,7 +54,8 @@ export const MintAssetResponseSchema: yup.ObjectSchema = yup router.register( `${ApiNamespace.wallet}/mintAsset`, MintAssetRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) const account = getAccount(node.wallet, request.data.account) const fee = CurrencyUtils.decode(request.data.fee) diff --git a/ironfish/src/rpc/routes/wallet/postTransaction.ts b/ironfish/src/rpc/routes/wallet/postTransaction.ts index 1649f53f41..ddec0d2d6a 100644 --- a/ironfish/src/rpc/routes/wallet/postTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/postTransaction.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { RawTransactionSerde } from '../../../primitives/rawTransaction' import { ApiNamespace, router } from '../router' import { getAccount } from './utils' @@ -35,7 +36,9 @@ export const PostTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/postTransaction`, PostTransactionRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) const bytes = Buffer.from(request.data.transaction, 'hex') diff --git a/ironfish/src/rpc/routes/wallet/remove.ts b/ironfish/src/rpc/routes/wallet/remove.ts index 89609178c9..8c8844d576 100644 --- a/ironfish/src/rpc/routes/wallet/remove.ts +++ b/ironfish/src/rpc/routes/wallet/remove.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' import { getAccount } from './utils' @@ -25,7 +26,9 @@ export const RemoveAccountResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/remove`, RemoveAccountRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) if (!request.data.confirm) { diff --git a/ironfish/src/rpc/routes/wallet/rename.ts b/ironfish/src/rpc/routes/wallet/rename.ts index 7c5be3445c..08accd9562 100644 --- a/ironfish/src/rpc/routes/wallet/rename.ts +++ b/ironfish/src/rpc/routes/wallet/rename.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' import { getAccount } from './utils' @@ -22,7 +23,9 @@ export const RenameAccountResponseSchema: yup.MixedSchema router.register( `${ApiNamespace.wallet}/rename`, RenameAccountRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) await account.setName(request.data.newName) request.end() diff --git a/ironfish/src/rpc/routes/wallet/rescanAccount.ts b/ironfish/src/rpc/routes/wallet/rescanAccount.ts index d6549e038e..a259530a20 100644 --- a/ironfish/src/rpc/routes/wallet/rescanAccount.ts +++ b/ironfish/src/rpc/routes/wallet/rescanAccount.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives' import { ValidationError } from '../../adapters/errors' import { ApiNamespace, router } from '../router' @@ -27,7 +28,9 @@ export const RescanAccountResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/rescanAccount`, RescanAccountRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + let scan = node.wallet.scan if (scan && !request.data.follow) { diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.ts index e0fe7b2123..3b4522e2d0 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.ts @@ -4,6 +4,7 @@ import { Asset, MEMO_LENGTH } from '@ironfish/rust-nodejs' import { BufferMap } from 'buffer-map' import * as yup from 'yup' +import { Assert } from '../../../assert' import { CurrencyUtils, YupUtils } from '../../../utils' import { Wallet } from '../../../wallet' import { NotEnoughFundsError } from '../../../wallet/errors' @@ -66,7 +67,9 @@ export const SendTransactionResponseSchema: yup.ObjectSchema( `${ApiNamespace.wallet}/sendTransaction`, SendTransactionRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) if (!node.peerNetwork.isReady) { diff --git a/ironfish/src/rpc/routes/wallet/use.ts b/ironfish/src/rpc/routes/wallet/use.ts index 350f004b54..394d3f0843 100644 --- a/ironfish/src/rpc/routes/wallet/use.ts +++ b/ironfish/src/rpc/routes/wallet/use.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { ApiNamespace, router } from '../router' import { getAccount } from './utils' @@ -21,7 +22,9 @@ export const UseAccountResponseSchema: yup.MixedSchema = yup router.register( `${ApiNamespace.wallet}/use`, UseAccountRequestSchema, - async (request, node): Promise => { + async (request, { node }): Promise => { + Assert.isNotUndefined(node) + const account = getAccount(node.wallet, request.data.account) await node.wallet.setDefaultAccount(account.name) request.end() diff --git a/ironfish/src/rpc/routes/worker/getStatus.ts b/ironfish/src/rpc/routes/worker/getStatus.ts index 6c7daabccf..c1676bf7a7 100644 --- a/ironfish/src/rpc/routes/worker/getStatus.ts +++ b/ironfish/src/rpc/routes/worker/getStatus.ts @@ -2,6 +2,7 @@ * 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 * as yup from 'yup' +import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { MathUtils } from '../../../utils' import { WorkerMessageType } from '../../../workerPool/tasks/workerMessage' @@ -65,7 +66,9 @@ export const GetWorkersStatusResponseSchema: yup.ObjectSchema( `${ApiNamespace.worker}/getStatus`, GetWorkersStatusRequestSchema, - (request, node): void => { + (request, { node }): void => { + Assert.isNotUndefined(node) + const jobs = getWorkersStatus(node) if (!request.data?.stream) { diff --git a/ironfish/src/rpc/server.ts b/ironfish/src/rpc/server.ts index dbc59fa02d..9965e6a73d 100644 --- a/ironfish/src/rpc/server.ts +++ b/ironfish/src/rpc/server.ts @@ -2,24 +2,27 @@ * 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 { InternalStore } from '../fileStores' import { createRootLogger, Logger } from '../logger' -import { IronfishNode } from '../node' import { IRpcAdapter } from './adapters' -import { ApiNamespace, Router, router } from './routes' +import { ApiNamespace, RequestContext, Router, router } from './routes' export class RpcServer { - readonly node: IronfishNode + readonly internal: InternalStore + readonly context: RequestContext readonly adapters: IRpcAdapter[] = [] - private readonly router: Router private _isRunning = false private _startPromise: Promise | null = null logger: Logger - constructor(node: IronfishNode, logger: Logger = createRootLogger()) { - this.node = node - this.router = router - this.router.server = this + constructor( + context: RequestContext, + internal: InternalStore, + logger: Logger = createRootLogger(), + ) { + this.context = context + this.internal = internal this.logger = logger.withTag('rpcserver') } @@ -29,7 +32,10 @@ export class RpcServer { /** Creates a new router from this RpcServer with the attached routes filtered by namespaces */ getRouter(namespaces: ApiNamespace[]): Router { - return this.router.filter(namespaces) + const newRouter = router.filter(namespaces) + newRouter.server = this + newRouter.context = this.context + return newRouter } /** Starts the RPC server and tells any attached adapters to starts serving requests to the routing layer */ @@ -84,7 +90,7 @@ export class RpcServer { return false } - const rpcAuthToken = this.node.internal.get('rpcAuthToken') + const rpcAuthToken = this.internal.get('rpcAuthToken') if (!rpcAuthToken) { this.logger.debug(`Missing RPC Auth token in internal.json config.`) From 7474a801884349bfbb3225c26cca8be0c3c4fbb2 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:10:04 -0700 Subject: [PATCH 32/37] expires wallet transactions on block connect (#4069) moves expireTransactions from the eventLoop to the chainProcessor.onAdd handler function so that the wallet only expires transactions when a block is added to the chain refactors expireTransactions to take a block sequence instead of reading a header from the chain thereby removing a chain dependency updates unit tests and regenerates fixtures --- .../__fixtures__/wallet.test.ts.fixture | 536 +++++++++--------- ironfish/src/wallet/wallet.test.slow.ts | 10 +- ironfish/src/wallet/wallet.test.ts | 34 +- ironfish/src/wallet/wallet.ts | 24 +- 4 files changed, 289 insertions(+), 315 deletions(-) diff --git a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture index 1c1956d985..704bbca1dc 100644 --- a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture @@ -1521,274 +1521,6 @@ } } ], - "Accounts expireTransactions should not expire transactions with expiration sequence ahead of the chain": [ - { - "version": 2, - "id": "cd8a190f-05bf-49fd-88ba-f722ef57ec1e", - "name": "accountA", - "spendingKey": "ea19200ac8a46651d97ed0ec8e81b21df11ed4c86d6f095e08b40c4675b85a01", - "viewKey": "830fb68e6883e35acc0bb6ec4a3ec6b1b217d378835d6822df48541a3d3bc25ad5179933331dba6301a8bd2a7ee4f80a58029fb339ec7ac1a2974376943557d2", - "incomingViewKey": "da72ad82f2d827edd0eb17db44f8b97188bc924655f9b64e8139a0df5d40f003", - "outgoingViewKey": "a8d85035fffeb0c3b1bf2c7a47efca3c46a498d6ef6da4883798c0d6c04c69e8", - "publicAddress": "f26aaa8fa9648e6bb4d2416cf81f4a0efa4a0cdc763456cf9e0e117292e0453d", - "createdAt": null - }, - { - "version": 2, - "id": "f6786178-8583-4855-8f5e-2a6ee4ab387c", - "name": "accountB", - "spendingKey": "1fe860198874db45c934223e7c7f0f80342b484dc33ff0fc2f28cfefe275801b", - "viewKey": "5699b6ab705362705c48dcbbfcf9c47f9997f04bebff5d957a4b39608537e3ae7bfaaeda5fe06ffcb8b5666bac53e33c7f00dfe2bf67c52e8ca9fb727b28673e", - "incomingViewKey": "465789214bb50c9f2f8c22a4ae951d7e9b72c0aef56d2a9bc01191b89f6bd503", - "outgoingViewKey": "1d9f2fb78618aaf2e3dbf80d4d41a4c2b1da9c64783d9eb455f1163a23e8ae93", - "publicAddress": "5c24252c381e138324a18cb61345336f36651fafc97e9f0d330fb513a8641da3", - "createdAt": null - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", - "noteCommitment": { - "type": "Buffer", - "data": "base64:NHVGjMBRj4JsOy9xqzLqf93laKi7VwjCHkPn2GxFNHA=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:mJYs2l/kcLM6bc64ak3s+sFa1G/ZwUEtKOwC0cL8vV4=" - }, - "target": "883423532389192164791648750371459257913741948437809479060803100646309888", - "randomness": "0", - "timestamp": 1684973364324, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAYCwLVLW24DdVdyLBBiPScHF+156u5oBtdJYEvC/C/WSCpXa9RH3puOoo5XBvuEzoltiUBUTuDwLBaQ/2xE428Gs312XJTKQmXjA05o83UdaXIzoPOAPQ8jz+88cR7xqgDaxS9zPTkoQO/6+27Oz8p7K0dbGt5+ZnHwqQJne2ldADQFXpDYh7UkKYNEe9UCXZx1zNei+9PclyDdWD8Uu/vMJBiLOO+Hqh8uEG6D4XoYqIPbX7lFUlQvqMrO7aE1Xjne9A6d4ZaYuP4yirpyvC5awn1Ue1cgFN5F8lmYKEwHKjbIBV/K6i3hHcVRuCPGxIlPcK+CJcssqzMmKMYt6IUD7dJjsF/j+h5k4fjFNtlCN3xwI0OlI8U4xgfDxsE1NLEEEV31dQYKPAZm6ZBL1vlwYfvHxpmgQhygtCq3LuWL5LBZUNZVXVgfLthYwtNajJYaxZWTCqhC8HsPGyuWlDJcg5JuFLlIJ1May1k1Y+XgrWUgN+EE1uDOXV2H0DAGdICfwhk3vzexxQu7q7dKxmM4Y+IYRYNvXevl6349hz0bwmfPKAe/tDWIWJ8Q3Gr3ESVhFJHVzq0IoVRPoaaq8pA6roMQvTPD40VVjQtq45DTTi3CLPat5VLElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwh2f286IaavRjHBoP4MTxkuQOMaOeedXh/Ql9VGrosnDiq2QaLcdr8bZ9yBkLhQIoPL6RybR8nFgpJvc6hZlRAQ==" - } - ] - }, - { - "type": "Buffer", - "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAd/xCNAOp19za80GMAZW+IQw4slTTrLLUCrSSpbSSIVuT8eGPe+RrKfJ0R06YROIHuLR69HlU6ZByxEwPQBkKSLzlFLpX9KnSff0oPGq0nVS3Ed5MabEJeszHOOzMN/cXf5vd2m5xxjAFRVGQWav8+gpQAU4dEhMNqlO7VjwIaQUMPpX9YeJHMoOkfuLgRn2hxGXdUZaLSD6eyve/rTBIA5QNjqB16S3i9ELz3L0NF5KvwL7cWJD1yUYkyC4DCMDyzEAnccarXNL2OExVDaSpDX/mBhpCe14oLzbUdXnl0Yt2DPsx0tEVHlG9xLGTw1660yB/HPipdgnq7Ou8GBeCGDR1RozAUY+CbDsvcasy6n/d5Wiou1cIwh5D59hsRTRwBAAAADZvE7F9j0okRzwyPs0Y6TMcneQQH049OZrvN05ZWCkMnx3hjyB3DeKWhrYqoxa2/Djswvg01xylzfBLvECvWSmX+jsW+cbcVvWlsQz3265sLIYSoH/FLa+pKEqYGqIuCaiqGa8Pnx5Y3PmZk8nMVvsRXWMVd6DOS8Jo2JwBWxQ6A3Kc35c/EsPIhjHhrSMrKaglCFKGHBanPf6WVPD3Ljjot9Wnjh0CsZtVSZl1BPt2LBw/8wzB6aDWC12ThDjijQag/HguJsVhAlkjC4MPed1qK6oD+idcgxlX2uFlHVrOOXfOTE4Gks4MT5zuDFzN5om8LGFFp9fLTZ1JVxjPWFgPWpLIQchEWQyWxHiF0UREBI/OWNSO+Vi/R5mfWLuZx55jE/hvZYr85qVcJc7BCrFTxZJVCD7cO3wYMgu92+gYFGPbmEqeDz7XlYvHkeGeKIJRnI1X+Mc+3asfgMtuRghGd1JMc2x+F2KZ+8QSUnkk6vPwiKrPSCFIbr4Y7LMWBL1sUfrGYcCw+I0F2wCaFFDlIWYi7oZcRIt1mjL7+JOz07+ERkVpGlv6idfozFwrqlWrNoZDcXy1lrrY1fz5PpLnkvT8pjgf3M3eNXLcmrBw56UXM9/b/LCrMcuA5kJVkkKKrSkeGd97EwbQkq83QVJydJVFmvKZhl77YMK1hCicf4NVEmXjOHGsm9HVxeRnYgXHlamjz5Uq9v7CCynWrXNx2PiMPd8PDL+ZExW8sXZpIAhy5uTi4WrYCr8tU7TKEgyzuSeadosJ7d2ZkdIn83Rzq0P6+2Rrvn+OuWgGBKF2FLwSPXK8yIKWZ1WIzqM5wI4+wAzyfDvbAt2H6WzjgxiJkoc14lscv1cmg8TxCR4iI9BUqq9kIWqPz7ewftM8PZozQbp/rpCAwgT9rNv4rFXn660axZm8AF95Yb37icT7za9jozmpoooELSCGl3spNxDJHstZEADYtflTZ87TI7Vn2S0pkHUqQhIW8w5hit6FyM4CrgccERGWWZemTBVpB8FtEAmZsg2QUXTDcZByiHm/Ttie/+NxOy2yH0EZajUJH2wdgJMFOW8DwDLeXB6juF/OziCgyxq73uhVgqphJVAykdMdFbECMbDu1ceIN1dW30u/9ItP6IUHD1+eOK8arCChdnud20dK8vmvKGlem2LtPHe90zgCZcC3TfKarTd4SoHu5Kmu4gBziPhtrB3fCg/wO60QU4HVn9ZG20PlbmuomqFYmfvh1FuX1uTeUr/9e35QUivTk4DTzhxgQNYm4/bXWnJBZCPzT9j4+lV/t4Mxz4XdYnr/eDrwq8SKYuMjzY89Sstimxt9T6lvTSICnPC1wu8dorgH3r/6xSXwSywv20Uh9FqkB5swiTl7OuYJniiBFXiFwga4UPnXBBCbidMJtmpkEHAlzf1xCMm4Qc19KkvLmKMUskisAQLGxhtV0DV0Jqdo8KkmgTExbL02Tm0WbviE6/pt60rz0E/SoTe1RCdkCbi/yYCaBSvOwVOx+5igqeT3IZU65yssmiV1QeHuuIMx0DDKG1nCghnUljuU+u8y/ehbjrfQ4uuINQhUb8H7o8MVgoZXfYeHquGBCg==" - } - ], - "Accounts expireTransactions should not expire transactions with expiration sequence of 0": [ - { - "version": 2, - "id": "39863646-19bd-4189-bf9e-8cc13c0e8866", - "name": "accountA", - "spendingKey": "70135ed3adff09e2e65a8faa007d94e9c99018f1ad927bf6f4d1bc3d08454b3f", - "viewKey": "6bb719cc14d5d85bd20629a046547bf0a5efbc2f6aef0f635404da54cf1d37d24ad912a9b71fa5c0a688e1cae51a5ec6e4bc7e4bb681561a3bea145e401b8972", - "incomingViewKey": "e37eb01831884c95a958759ba334a16a645e7c4e7d3a43ac8fc1b595962ac300", - "outgoingViewKey": "6f567f6e9af1b285955e1bd5aa7853087155e091da5e5ee57d3fa2b8af1d6ebb", - "publicAddress": "ededa776064bbb79029a0d8fd8c1abae591677fee044a32811258ff5166656dd", - "createdAt": null - }, - { - "version": 2, - "id": "cc2cf8ea-27e7-4229-97dd-da7df7ef432a", - "name": "accountB", - "spendingKey": "bedb18706c7ee90ebfa3a3b6ab063fb5ed01985c0bfca263eb1171ba7b339099", - "viewKey": "79687da757efa9ade7ef4c1dc2a7e66b00eb0c684b76353a8bbd112eb6b3a7cd0cfc32a5645b6c3bf7b133303ff45c849f4a610489a3ad15cfe46922254f593e", - "incomingViewKey": "00e96dc2cf31a48d47594f1106be13087d37ed44c068ca722857feb94d9a9802", - "outgoingViewKey": "200d56f3645ff5a756d225031b965f6325a483f3fb2fedaadf20c8c4e3cf65d9", - "publicAddress": "8a495ce788f9bdae8166e2e67de4854195f59cd2345269551068b3e9e47e3b47", - "createdAt": null - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", - "noteCommitment": { - "type": "Buffer", - "data": "base64:bWzd9luuetAzNZk7vLGPlVnlZfxOUI/nTIz5oWMrD0c=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:gKrZemhoOzkESib4A5Y0WS3XppMWpRYTiisyec95YYw=" - }, - "target": "883423532389192164791648750371459257913741948437809479060803100646309888", - "randomness": "0", - "timestamp": 1684973367688, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAmHtGPVSCpGsP3P/7gI0sU5tAUi3r+9eFZMT/xDBkl+iJHgCVUzWJg0m9NqT6zitaMlZZCwNeuC/R+I1zxU0NbcBATOLjgXeGr/VKHduyisWiieIfpcfsKKivQbkPUinMZ0xcTF9IHpexvQxnzULDdDNR5Uimqd2axOEW25pcKvsG4yd4SRJqrYtDGR946PZZW0Ab9v8QYBlGJTqHkC3YF5BhNl2DoSUG7G4d0s3+HQyP9C17w2RFwMsixHaWEKc3cvqTERwgNcb+nrhXgKtK99fa0KwFx3Q30b2TRa1E/s7TnecQkxKHIyJHzFqR1nJXJY9tWCe4xKVaMsqfKxLEb8KjHJa3/MPRq6jATSK6SwSQP6GNwTeEAay8lr0CmrxAHukTFobwvYksCJif9kKiR36pbNcxOtsT7Blj5sYifl9tQLQLyrzXaDz5JF4rqHCJbs84aWyWszw0beuulTdHZFe62oCI47ZjvD0Eyqi5yPC+lxBRvyCyvAXugzlCmU4FuypZWU67ygNRiNojRDyhzg05ooY5tVYvN966lj2Y8rH5q09uxLfH7qzRwqJ3+Ih+RAUWV4fKp/PjYf9MYKDmkmvj6mw8mXG3BYBos3rXF6zWUPwqhGHXYUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw5axH9ilRPhrG2e3hqfzky4IZbJ48FfMxHVsgrc342qmLPD745YUPG0eR/2AvSzWfjUVT8U7OfR3V/a6ihRR9CA==" - } - ] - }, - { - "type": "Buffer", - "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaMw+A3cQf2vPMLHM5NTXeNmwlh1RqtW9HmVvZigKh6mZd1ry50nQLFV/t3jhk/HcWnVaL/dP2S43k1DlEeLiQ66JiKYZJsQ4uT0OjhSX0CuPJCkauLD5iQbiIMxFmWafB6xxJmUbxp+1PAQVM/uvM7sgvPBTJFF1s2GoUgSCM90WfUVIjr114arRVpOBzJ9hnptIN/4w07TefVlCPDPmwrbtE2PONFafQNzBwooysKOp3KGhBpUnmSP3MLdrt6LA1Nd6Lv/4qRFXUfBd88FLqZ90blu6B7DyB/EpXs7Qwl/N4Id7XkdlT2miyWXciyz8vO5hClF95yjUV+eHW4DRGG1s3fZbrnrQMzWZO7yxj5VZ5WX8TlCP50yM+aFjKw9HBAAAAM4rjlwIPICx8cGhUzc8vy5XUpzOR2QO0HhiqNcO8iV05O0MkCSXXb6Rd/mmSP/Dv68Mx4tzF1SgxKvf7juy3dI8A6kdzbdRQMgYwd1zrY3kpxE0NG7v7j6lY24T2CRdB6r3dRemjx7UCSEbvx5Eo7J2udl1F77Zj4eSqZkLlOhsu5wZai0YevDpDJYIJiFQu4E/06vs5TSwAYkf5s9zMKhyUiEYr/SyWXARCrhyxwCQjV+VW4qmErL/ME6FfX1V0AA5SRGrLbRLFt81V0gR/FpfXzIivWNrCwdKm4jp0+FB6piEdwdH3DwsXUx/PsCzh4Q4EAtiu/gA8uPofkV/SVUBLsIOrlL5O2FPyrLqwwlrm1SI3WRdI8QxEOC2lDPglW481pB7KnIxYB4M8kMgt9739DZOlCZvoQrCtCnPmfBXIQgN470vLVRqfnYLbzl/IgEsrItoRf2n3gq5tl5e5hjY6vP5b9KaE+8JXfuqpjhD5X4YcidKYyCEJ7i9Ska178kAfoOGAhJhwy8t2KLRqvZhVBFDZQhTjwNidNZqk92maq6p8CZ9eQgC7tO88RuKGLfRx6fFOPrtqxUnnYnmPm/vZR1WO1Vo/KPs0ENQtCEvDbd1YWVSeB9sHeoXteNMMWEaExaZscslhavV96TVebj4KuUhspvMSeIHY6OOXJbXvrQVPGS68oyRqQ3gIAFFNSXD6JRA7WCuW+jsaXFI4atRlEWEUWfXi5wfoyckCGqdtP9BIbmpkpf8hrUdiACpLFIgK1fIM2/A9rIZ+J7nrGZPotP6kAxO2kOiMnSzB0P25gBbPIOeSECyO9dW2IvmROZSedXSURe2u5Lj3YihexSw8auZOFNa0wI0u33h8F2ogwaVRsyNMhGR7BUlUqUFud7ZMTZ8fFMwgtDpByb2L1jZ1awKLMofUk0zCt9qiOdNzWrGBLgvm7sWm+tnxeHnFfdYpes5RGZdvcn1cNQ0/M7GutqNQyP6fkoacR3ibplxcYigrqfb6Wenl3ClTb7CN75MljUx5Lp/nfSxRFFfNL6/PfTTGT09s6pL7Bc7O2h5ZfPQgzheh3a0Dw6q6P4zcT37ForzwW4T1FMD26LUhs+lwau0j74MH2znhSfl2qARIKoaQag7qo1o1Lm8A4GaE8qxRqhj3w1O9y2Z5pUdR6TAX5VAHgkr+0wNdookYDU55FVah99nYEQ8v7uNpxUyjvh2bbudkHTlG4GFHFK040bDH8+57vKjWOMLeaC9SBd2i3aS6mrDf+FVMISv4Ono+UEoa6Yblg1UnqACk1TVG29SZV6uWcruwU/LWc/+fVm/UIrZyL46Neftg5XrgRZ6fG7e8E/k2KCeb1D4a7/qAh6m7HQkGQ4VXBfL9nbZjBUcR/1pUV2R5z2o9TRnnOjB54oeedKmk7JkNOZ99GnaSmNp5uuHiyi8JUVksH2D8Q064yuFjtUgRNgHn6uPeU3helr0+HOii7WuAgG5gtGoT+vWtQccUwxvhzdC9LVcm84rRu+lmkvJ1VKglfx62295j0vqCsmROWHqjjDOZF3GSiI7Xs3hNsylWwiGyKOdEZeXrpOq1b0DxWghud4SM+cGDA==" - } - ], - "Accounts expireTransactions should expire transactions for all affected accounts": [ - { - "version": 2, - "id": "0cf16b43-ed6b-44d6-8646-9b8ed430547c", - "name": "accountA", - "spendingKey": "cf9391cd1c7dd0894e96a68d4ac02d735049cdb1916af6f003ea9045f076817a", - "viewKey": "bae42fadc0398567e123b0f4087f20c5cb2f6e5cd935474cb1122cd545f4a3ab5d38304dfb4d84699cdffd58b3173b062da2d118bc2e8180966bcd5c26a5b8b9", - "incomingViewKey": "db959d4505e6f3c4bf912338f3057f5a9a618e37a197e86930c2876b974d2705", - "outgoingViewKey": "ca40cdd740a871326805e1de46944182ab7439aabb9b6638222e8870b16030d1", - "publicAddress": "ca732f355bc6317cefa4c9acbb8685802b1c87a81b6665bbdd365237fe1fa9c9", - "createdAt": null - }, - { - "version": 2, - "id": "a135bdf4-d034-4589-94c9-27e289725827", - "name": "accountB", - "spendingKey": "c346b61bbce3f76f4e18f551cf6dcc74ba7b346061d4a74c98e993a459074fe8", - "viewKey": "ab8257ecd54436a6eef1442627c79948d34b0b60e9bd4db072f7e3d2950430077ccc2c0628ba23c4a1af1c29528edf300a35eb294e55c2febba346a16130cb69", - "incomingViewKey": "d7523fb77e290dafa06285c7a3d0004c7ff6ef080ed74d910d9a5ba5f3a81101", - "outgoingViewKey": "eb0fd25162a4c85b156298219b6e7ae61b488b6000f157a302eed27f135dd346", - "publicAddress": "aad82d16ded4ab62672a2c97c11d3ae0affc9e0307993b95963cadd096c5c9bc", - "createdAt": null - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", - "noteCommitment": { - "type": "Buffer", - "data": "base64:v9wgXlJOsukipgXMTj9bTrV3HqT5h89TB0G1cvG8qFk=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:HNGNtwhMtWJ0VHjrhSa4m8vgAiANRAdL0UQQH4mKGXk=" - }, - "target": "883423532389192164791648750371459257913741948437809479060803100646309888", - "randomness": "0", - "timestamp": 1684973370946, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAF3VthG0HAJYzezPV795o4y4GzDmBSrEuRXcSKOtqvcqthQJZZd6TH3e9BMrFlTpyhUTrIwYDp1xThyJDvE2hgFWqFzHr8p7ysydhE7ERI2KRr2EH2W7geh4amslhR1ujPNd6Plq1P7b4YBaQpAZlYZzXEzSM5FQ3cM6pkbacw8oUc9PolzHhhzZiro9bInt8HTbkobaC6tp/qV8ts+kjfv1e82UrnxBHRBlKyewGanWZ7D5Yqd7Di/BY1QrQI34FFgRKUlsp/e905GfD5/q4B+H1XUSRFNguTJy0Tx41chaK8A95EvjQvKsVbAcv41i6x18R6VW9ctRFX/ICx4YRkkGKveUZNqCkFWeGKm6cIbMsJCX9zcKACVse8qu4PQo3V1AxdbUu4GLIt8S5X0q0UNEeWZjsRz2gPxTVVqQGH27KXatVMfLDzWrzs96oUG0ivRrP+0bLfae+kr5baPZr9lxm1s1qHWPUTr1QGYr14wCh8qViVMSKxki0AHVMm1lk57ENwALVLgDxV/sI8dSak2KtYAeMIN2d67Swet3KaT1nQh5CP/CIqw5va03WuKaeLU1+1LpmaGNLhuB9GlDEEWchrd9bSVtZu3Poclu7eFRot7evlR93LUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw2W9XuiZA5Nsl5qnN6bT47kW4UzstH+Yvv0XL2pYQJ01/3QeunExz6qEuKhGLub2PytCrj2mCfOkvG7ZT2eejBA==" - } - ] - }, - { - "type": "Buffer", - "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAEhcrYVdgUptB4ekNYfXVMl8fE3axAETxVXhLpHQzD6ON2SrTW3Oel1BdKox8gjo+49H33EP8cq8zrI9eH9b+eeVaeY1DtyQhC2PVz7lPooOVpOESvO3sQeNA6yk30YJKELNEhvlVQKeN0LaPLGMppyHHZBMG/jAEmaigBViQxLwLg8y9Mpju7AstxNihboOpB56Z1FPYZgo7c1fXMyu5WZLGBOXuMFOjwhQYoSxXF1Ch5zBA+cxBmqC30W5LD4xdwGYrqil2fTn0sVCQNkGREkFxyKYmqlXZc1HSGv4+2wqr3aLTHsk2HQzRJDrHufnM4h4kZjSD695eeF4CmLddJr/cIF5STrLpIqYFzE4/W061dx6k+YfPUwdBtXLxvKhZBAAAAJwUXGzMXa5M0vMsCfLE6jMQDltfBu58ca/e/0gjA0JOj8fDK94574kIQFKJRT3A2jnJwqJnz640L4+leLbbbSwryWip/ogrvlrFchI1abuh1xIli7UpKyICc1BInQkxCIHjy+dFSaNOfwmz6vAc9BAmPtiHf7ceo/bxumh43wsG1GdUdLTea6vWZPd2hp5GRZX9V1PqIZzhoE+2WrbU3uOtJBXqWnFr3uUkCKb6ppYT1A7Z+S40tNRW6eFWaQgbFQTCrCN6a0hhsNVVNhgai33fn5+i8YJcU8UsurIUbOoabFbWTZ/hS3YYh/SwOs/Cc5USDERy+V6jyK/hnib+y8yXQYXDs48LnAzL1GnoMFpu/OB38Qn/5LL0USvH3XUJUSz54J6sz2E+OK/NZEQq7MthZVbAyLJFTvyTzJzz/H00nykBA9lICLx/iN/abJbLa5OXGLyyvucJ19ikVlqwvSj0vnt21SK8q9gCT3xkWhiwKFn0WK6oV/XNqNrUZPfwUbV/qLDTJSnibHw8cNkFWbKmHG7eLU5F2LWxIgZJaBJMHwFSV3XuXA+Dy8v4Z6/RVHAIEFt6MD/bkTcc7ATtR1j77xWSUEUNYQF0TBaIfDeU8snWW0yxIg1ad72wYvG81N5dlqYh0M1sVOdoLX4vnQTPUdIQjDttyEgcqzqRfriaiqzfeW1tqx3hKc9QCoCNFyX2Pv4RgFOkV2qzJjyTL+dWThcFyMVzIkySWOcOPqfmw1xLqCD9AmFcckkf2qmPXag5C/FwOrD3APWW+VveoDnFeuaA69wfHHfaV81CIr5QXMVyTrz1yr2nZsQWR1yMcCAljtkIi1p13AZkQerHxTFx4Nfq85zEiDqLVpfjs47HjqCCRZfpsHaQ/HIieq4V9qQWOFrajlB2eltjMe8R+OmgN3i5aya3kgjply6I4ggacKOAnVgxh5wNPTTiLs8/KigIQ46VSmTQTf3O+/Ao9wjkElXWAqRjdiGYOw6VONDdtftYkkhhkYyCpWc7n1i2ups9ZrBoFoT1XW2KcVtZkk3yTUcPU2a6KjFtZUpviW8VcBnmr7oEkREP5rhLAAHo5TGzaqiropNpse1SQagfmc8so/w5M8k7hdX5rG/XV1i04BYJ1Rs9+4oTysvahnuIehcCbWPseMZscHZEKuFLWH4VpriQcGP9VDEDi4xQkfZ355mCCExTHKECYaZmginwzGSpHeXMRbRn0C+Hfej1NXZB46U1yjtaRIoTskXvcdfVVNiNTVFffctloYnsO0PWcnW8qFKpogcsRzeLhw/RyxYqVLcBf4BnbOkI77wpt97neLkkoICpfcLXaQlYSnCLi8qTdWZ2rb18jgTzlLxt+VhJazQB7XmI7OH7mo+q0gRgFZpx5TgepcE/UP39LTgcHiaCl9xE/deP5GWdAajFhmL6juOIRx+BveVqGigHK+goKeB5a4Qx1FA42ehHz9mrypoOyFmUhqyBjVk/SGczON+RPMIYQxbkysRKzl3EOaOyjLgzi93DLtkZba9MCqvIzPMypA+uxWTK8njGM8cNORk7PMIY4Ythw9geXDpLTAcsKH3Ox0TIUCSpxTYlAU90Aw==" - }, - { - "header": { - "sequence": 3, - "previousBlockHash": "4B88FA9A82CECC0E5A99F8E436A932E3977A4AF3E25B8F6FA807D0C30BFBEC6A", - "noteCommitment": { - "type": "Buffer", - "data": "base64:sRi/Jm/3kzj5POlgrLKHsiamqT034f9xyelQGiLnyCQ=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:xzCJb9rKvEjKljgQs2RxzLOemf4DbgaAiX8ofWXThvs=" - }, - "target": "880842937844725196442695540779332307793253899902937591585455087694081134", - "randomness": "0", - "timestamp": 1684973374066, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 5, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAA65gS61dgyL6ZwbihsWGUm79yjBS6ZVJV4ze4/yFwmmudw4eAgCMqNXKjBQyxVGh/wp2UqAGuqwncx3Xql9RpCl7S5BqgKw0G0mBHdSLNUKTVRKjgORUPxJNqIn2MBe1e3c7Y2MQDuSk6tGJa1AxEu/1FJnPHmELmYvjX7fkrzsRzxnTANJrUs8yFrNjAvHsBg9YmOF44YI0MJSVQn5jS+0CYf/yqdx86NejS2pmL4ewfIKlSdLWb9guc+IbTWD8ev1+1wWRrPzQWXYRlvrbz/jyqzVbQe4Q2bw0hKGTxDUWQJu1oQrAv4ee8fci7BBsLCl9bFhjEPgRm1jYZ7FNUjJmejTP0TOKvIDyHJczeUx3ad+LMy8eVFi6cCg6X+RfhsOvMbbwn4fbysgPvUgh8IB+ywSNgJanVcdA7/V4iRt8tRGB+Whw/8bdHmLAeAvBRb5vWc8xA8j7RYeRQnQy0T/Wa6MR28pc2oW3ou1Lq3fdc2i1Lb19Dl+IUEdDczNFIgYr/XJqmDbKALITNgO6aUbHhHXJGiKKadpv86gMTj84VlPEMkjtTKcF1thj5hS4awVOYxn7oJtE2qsN3sMPgeeti80DUf+KHTl/nqMHbCvrhOqnyF7/rUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwboVtrdbP8irHJVxanTza4tNzFb/6qRUR6+3drVtN3djif1JgaimU2FhA+CqUaEtzFQt/YL1UD/pohd9qI/kyAw==" - } - ] - } - ], - "Accounts expireTransactions should only expire transactions one time": [ - { - "version": 2, - "id": "b281df48-4f02-4916-94aa-4257e69c020a", - "name": "accountA", - "spendingKey": "aec1ca8f6c4bff63bca61db5e2f51367ab719d13d9676ff643c8404475209251", - "viewKey": "e5f6f465311092b5e8d4db30ee3b47fe32202f866a0618aa5db445871aa2abb4cde464e76614bf1a6556182c9c84091c6a5774cf71d1c4e31ffbc7db134037ab", - "incomingViewKey": "e0116cb60427f69340c960b7304d80fb88b6912227612b7e98e3d26a7325a207", - "outgoingViewKey": "c9bdf82a82735e58ebff20702ae31bf72e1f9cc3723c2121b49c63a844f73d4d", - "publicAddress": "45663a03a342ecf7e2bb9084e0457dc1387dff5fd765a84b3fa08b764fe6d06a", - "createdAt": null - }, - { - "version": 2, - "id": "6630706b-472f-43e7-bf2e-c0c8286a8878", - "name": "accountB", - "spendingKey": "e31ab83337eca1f4952c6f873bc8e55490cbb8d65b8169a5b4ed8bf349d8737a", - "viewKey": "1618d3498fe5433bbaadd180e9be72582eb8d2d953e7820fc436cf33604a1913212adcfd4952032d72f6dd816c39fb52511a42b2a362005878cee366bd956abe", - "incomingViewKey": "f1b82cd5a81304600c81518cdc1cbbcd79ddea673a65d92cd8d7d9a18dcbd902", - "outgoingViewKey": "601aa9197cb857375936df3105174808f3fa5835b49db1b146d2fb982e748feb", - "publicAddress": "91f1dd1c9a406f4666793c900fd33a46a843bbc0684a721a676db9d8a89ef38f", - "createdAt": null - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", - "noteCommitment": { - "type": "Buffer", - "data": "base64:qmjBh2mWRJpmWfeA45asjIuOQoHtVz5lSWRwYEvliFg=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:3N4MWDti7YRYUgAjDC7eSMRIJfgNTBfsAW+zA+e2LxE=" - }, - "target": "883423532389192164791648750371459257913741948437809479060803100646309888", - "randomness": "0", - "timestamp": 1684973374857, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA4qWuRB6kFWd2hB4EKlt+x1LQ7DFX6VI0y3fHm0ALLXKK3UOklSQrdr3exEfsoim7RGoT4bh580YSCgYDHAFuG4HpLmGyIBEQigMD5ZRF56ausIztKRWdjAchzNXy3Re7RpR/bML3p7SC95vx3YVrmzsbEn0WPPUcHCB65sXKKD8PHOSkYtMaJRl89z5Xx3e1ALYY8XSPK1Zlh9Jw6c/UGgFkT1Q7e9KiF+VfGONyF+WAt7DGLzwKMy6rsjtj/sKrscsILjxr/q1xf3OH6qRqf/rOCh0Uioz6SKwMTyzG2i9DRiBRmBIelSO5g2nAd7E03DXnlJaD4Ncag0Puoob3rKSgZfR8hmMa7d9VCiXRbnZG2dCIYxxg0ZH+XhGHZX9y9QpQS7tccvKkypasQqOs16s0dCEM5xxivWsylWLRCYfKi0cYlVwVv54NVMrBkQOUatRKnQuCS11Hci7c1+lpy8j8QJe7anV069c3u8hEEu51Nr8iy7xe4zsrKB4l1EhGvIS1gRhtx2JitwAllWCswNM6EmQXkHEwUWVLiSIOLihVB6jZVGFSQ1a/HodbmRb7TeE2NJYrRoKhCIwGTU8hQ8ZSb9A6Ydb6i5N3XnHSV27TO9YFpokmsUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwron7yR25Z7vZnu4aZX6cM75U+dEHv0snDbzkOMLoQ7WVfu8EOBXvURikWmhjv5X1lyKDjOiX5ezcxlm6c7OtCQ==" - } - ] - }, - { - "type": "Buffer", - "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAACmkfeFlIfl08cn1jZSpF6fQWiCf/vxZU3idhRUCdOL2mGI34MbBpBrTrfGbUHgsVGJCZxVzp38oLXhL8d6bg/Ota7BS3OKTcMo/MdDGTFbujW/x1OqBxeVF8aEQPHLuGsKzhlCWMCLKGwiJOMbCdJLkYMh0+O5WJEUofKEj/cqgX2FdcxoQq3fSQ4RwBl8MvRs5eAqhOUP4mFW+Hvs1p1i5c/lFi3WpesphQ3mERk7ulo3HAl3oNKmraTs1WqFKCDcTG5N3p/0UZLxMKofJ85iAPkkhgDqX3pRoVHRMPziKGhqaOPNw+teWVmv6LOUoVAcr59n8tCHaHIARDPq7iJqpowYdplkSaZln3gOOWrIyLjkKB7Vc+ZUlkcGBL5YhYBAAAAI6L0n4mzjC2KcmlzANvCj5IjUfuHQS37jmQlqUAMGsrz8JEzGsJNV9n1b33D4cIrYKy7Cp11rvWwlQBZ0peQcPzU9dqI7HjO2dMsTORJJvhk3IbVyyYd9zze1GQ+PUyDqRLO0szWyPLl122UDGUFolHtb7ai9efHypoI83DgdJPZzYa7VA478N57c0WQTFALY/AvCPX33ZJNWWhCtFDYgfSZK+fPuzfYW1X57Y5IE1FdE75tOpjJ+0kxEdo2Dp/9AccOHjtoAi/WmO0XrK3xPTB9FDrqUHR+Dt6kEI4xsgoKG/h3bgffjFrvqQkk40tKpWkVU7emUDUZ0+PbeOdVRcPaXYOGWB1TeWVq4MH9Qk5HSCmKofiaPLU3BwkxnXGzSFPkLZXxEm7ePHBUeVLRRo1eKKOkMnOO8z4XbTYmrMSBWYwZ/ulmIBLoeU9SDQT7W2LEADQkCC7hLM/JOVsegBOfO5GwWU0n5qSu8jF9S6pgpsexOWfd+NMK94JNkIiLA2jMiHFN0NuZSaKALgYG0aY/kgoZCAQC0Zwcb6X7rFzM98XXdOJ3oWBBoCDVWQWHYisgt00/Z+s7XmDH6vFq4/jGrKiGcYTss5hKrbsnExjxipSUgTPcPB5bYGUlzuHCl+yeMrV83DUsuNIUgd0o3YCojlTSbpXYXHgV/7Fd1Qc4tQwUljiBdKmHrTBFZDl5aX/7JRJydcQeVUqNVpZadGyfv1/M54EybkGIiuiyzFcWbkaigwRNeHNJQIj15WzEWHPds8v80KkNoKhIYyXQHK4Thx6vs9/k3ADqqLLwOtV2YG9SosfUFyLZsDsH3RfoPbSH5Lx8glBwRAzqmaGvhp/+MclXWv/+oSUsCTF/yVPk5apS1y6I1iVm+avJFEg8cI3U1MF2ndAYgX+5uaXJ/YNwi7HHcZWoPxIOFopLHOr0Bd5dZRXgksTp1nXLes6ZoLWL67ACzMBQC6IFoLn9Rpwbumt5/1hs0DjJBumdNBdaA9laE6dnLeGNTv9tHOHctm+8fq73HAiHJzZZEaoFce0fb8ewDgW8NW5w0TmO9raSZ/pkJdSZzz5fHquLCxTJgjlaMmw7WwtXjAl3VKXvDy+cL6q0FkPx9d/tmlPa1rWnhZQqJfBjW5I+H/6Dd3Y3uBXLqRfhEg+mnz9IZLYTfGi+GxfQy7+ShntcUIBFcZ5E0k4pdO6NspRQvYUhldLztH9BI1he5xi0w4frqeJno/43p/mKIITpsKzE1M5PETeMwTHhYhnWNXXogfmwbl3wmCLlXyukLFS78sFJkEbItRLmJLv2Kw5WqBTFBSOod+IODV0gY94ZljSI8AlNmgFigIPgIyxu1ZQjpKiBVMuz4nIlCRK9jEb4ifAppWWoKxhRV1k578DmF2Wgo/WErrVKA0JbvNfhNNZzvazbwvCBzT4Vdi8Vye/Ug8Yx5GUJvTVepVtgeZW5KnO7n3FHxHDd0iElVM2yBx65o9wKMLe3Wd20efhLuVjzzLQKfsKXeKRNhLHHej5bqrspTFEffvb+ueePOvfdk4GCHzEYE8XNadW6nZfL6WT/3CgzRVlkXLfWlTPlxqHH3wHJffqloWdBg==" - }, - { - "header": { - "sequence": 3, - "previousBlockHash": "AF9D861FAD89EDB8B23219C15421FF7FA9D6F0C1208D2D54068551A8665AC928", - "noteCommitment": { - "type": "Buffer", - "data": "base64:5GejhAoWUJr9KKvwESh4RuvST8RQfuzqXsLvDpm1EE0=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:xQZADXHIruaR90sfuwy6PKSLYToujddybmIJytJV1Yc=" - }, - "target": "880842937844725196442695540779332307793253899902937591585455087694081134", - "randomness": "0", - "timestamp": 1684973378180, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 5, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA2xRtEFxCq3QTEIZsioEYik7d6ZohU/cGNTyV7Jh0x9uBo926smAbFpldFdn0hPbZBZa0SwsvTbfu/VG9OnCyLCAa76+v0q3YvoYaY0DkDMKHIhhAMa3JswYNb2DFOJvehW05FRd2qax9sFsqpuzZeRu6qzYkuqV15ceK3k4ySLsElIidt4MewLcuRCzeK3yZwiFzJC5u9ZzxsYnB3URbaW/UzJUKhvv3Y/EdaOl5YOSuzpbRbkzuNa71VadNSZoI3SKIIaPViQ8i79QpLZMonl/+pBFa7KmZC/v844Iw2aT1oeoQHMNJ/NEHn66fw9l/lwQyGWdYbJDxHGbbpjbmm01VDpTAloHudEaYyqqiTLpUAflvmFm1taDRmZqW++ljGdwNYafL+y//29FlwrI44iWSvoK9JPwildggQtInjw+xB5wlot1Em8EpfGe6f+ygVovoRMGmfhgYUPrFDVP+zRyA9/5ln1XxfN1fWL0EucVjhLoYjc8N1WXlQmtXJGR02pm9WnxozZ19MfUFdf7WhFBlBzNf6+r27rZCGQSv+gIaMJzXxp6KKXpw8dHVDQhoiF3I9WxaZWCuwHrxQ7HgtqSpQk2gHhAl693BVHQnx8fs+mBtdClrxUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwkAGyq4QD6FuBKFhDdMVh5xyjgyUGJerUphPVUMho8bUz10ctgDrb8Txn82Z99MiZdRdzoGnzTH2og0bllP54Dg==" - } - ] - } - ], "Accounts removeAccount should delete account": [ { "version": 2, @@ -6032,5 +5764,273 @@ } ] } + ], + "Accounts expireTransactions should expire transactions for all affected accounts": [ + { + "version": 2, + "id": "a7a0f64a-30e1-4ff8-a99e-813bfacfefe5", + "name": "accountA", + "spendingKey": "789e9c80f6f953fe89e2e9f53e92c0c2f62916c6d07233ee7f0972dcb0a11b54", + "viewKey": "9b40fb2caa5e1885bf81ccaa58bda73fa357ac0300b1be1da82beb4b5fa72f2d307dbcba7cd30bd75f9b1189c6a1587998c4ea0c8f7b7019b5088dccd2c201bb", + "incomingViewKey": "2cc5e5efdf9976187c100ef56b78cd3100b6c2da466e767faefc529f59cdd402", + "outgoingViewKey": "a74c2947c47abd2cf7590460e14dffeb3965f9d2e9f25db7c76fe2d405a8008c", + "publicAddress": "27964b0770b9f4c1427d3d3322de345438c9e973aa3585544aa7adc60d392168", + "createdAt": null + }, + { + "version": 2, + "id": "758efb80-5eae-49a8-b920-454dd7be1740", + "name": "accountB", + "spendingKey": "d8a1dd12f316130c8342f2002aeec1bd56b261d24ba5b6dad8dc6ed684277161", + "viewKey": "3804a906f28f5c5e71c482fbadca0c129830964c531ca56b85ec70fea7164d889345bcdde5ba149a5e0ebdb2ae7e2aa3d204b9c6e293968818a71c25d40c622c", + "incomingViewKey": "86bd4e05922dbe8f428ef262867a098bc10106095353bd1da7096fd083bf5507", + "outgoingViewKey": "5ec7b45de8913938c126b3aba7cc4ea828e9224d1a54e9f24a9217a458c56d43", + "publicAddress": "58b002440143a60fbd50544c47ab14c8eb39e11f8725a740a57e38991fa8142e", + "createdAt": null + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:3cfaYCoNGN/mUwezZ4+Vmhz5ittAj9fvVdmbE/tgNDU=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:1FmhMjs7PvoaLY9YaOGQ0lwHBq/yHJfO/YmgPvM+HHE=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1689367131509, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAorIuC6iOXkG83MY54iltbbfFDelVQxvrIuwdiPSGyz6UCH/dAvWlUe0R24IEKgX6y3wRo+Cy4uod6Pjsge0wfaLukEg217puIdcTFpLxzZ+i/miU+mW0wH0DWlonQFJZ9rZ4NqlAJuacwmZdYwHULpxSEG8NA4z6wzGpB+Ynyi4FviEQzjd4UQW1NZTNhldrowXBTNC+t6gOoMM3E9WwvDnF4oIeE7lzdfj7V0EySYuAvkiM19SUGvWocaDAU/Kr5+VjORD4LeieMaMHi9C6N85+Bitot4R5GtyZDmIn8rbsn5ASC1gebyi3UEQ2cJ3pK6hKwQ38VY/rSaeDOsRF11vTBKyfe+f8zi7oHvE1cakqITs710rMR7SuqPs5qI85Po3n+/J2CbUneFczzKwARQMFK49xgRRvlejPINE07pkEKGhlUUNuNuIDXWMzHO5zWRV3Wdn1CWfus/ZsnLnsn3NFHSpP5kqKz1BL6j2g1D1Mle+LWED0a+yV5j3QdRfba2OcND6q7JhnuEKuNxWGOjwd770pGvoeWkQjNgLq9HoakurPLVdE7cImITcJkJnfD+SVyM4zuJcCQttTyM4ORsZM7kxld6jx+vOECzPK2OlbTjAyJtIwyklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw6/uvTYpQZ14M0GX9mntWTi1TaA9tlg3xqv+y3kBVhKetP6iOKSb5V8GN5Xk7DoiifUQVumKDQmTRQrTLzZi7DA==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAtf8NGmZ8GXvHcKJ1shhTVwyTHQPTQZlyI8GUHOLAKWCVUe+s4M9PAGPModd/koQh04NY6MFmXQlk/Mnwu6ceY+ntLWx0FikOUXm28un1YsOMpQQADxnye4ESdmhSH7R0ZJf2jbgWTdakJvhGtJX0fQX4KyPe2sZliq9i8rML/6QDDOke3HMLBkSb3Iu3JKz1JR8dpDb9Kzk9H8v+f8Qo9wYiOM+iJrLKqYfLpar7CAW2nZNwfoLYNuSB2iSVDXi3bOwJQ4SXJERR/SzYegObGEf3JshwJUdkPYZ/wnwqhuWkmpWHpmYpfZLBF2Es3ic+kFoMu4bhywEpnFxcS6AQod3H2mAqDRjf5lMHs2ePlZoc+YrbQI/X71XZmxP7YDQ1BAAAAAOkM9i+X32ELUHQhau6iVt9ph4AR1jMSsC85dmzlx/BWH/cPYsIkpaKolqBJXuhM2dboEDVqYXzvDxynDDsx1VyGeJqlEO2S+BPhGlcRk2NWTkxUYnDlobYeIqtH8RKAYrHaG5+NWNcK4UJiPwo8fHEq11C8DUI0AtkDrZvHxcx+IQ5YOrC2MMk1eIxuwRb4JgAT24VVp2orPBGeRF6FswjS2wdadLJtVuhsEaAC37wZbOUUdZSH/dMbDdQzaj1VgXDD35xXW4qwpjBm1eGynuxQNOa9i4RWOqf4Wy2twNbQIZpnlRTotPiUbHDG6pwPKVEWWC2HoD3iJ+O9Gmj2u/t49dYp64sjOhGKBFFBVD7M61DBy0K61ssKphWhqAtoFPCWyFs6AJvOv5oZDn+unrEeDT9qXa6orFVxaZcT1bGWsxvaLyxOYQeeAFJdZVbQIHsb4OeNZidxxiFOPHSEBSGIh1U8O0lFP013/Cp4jqCTIWihcxlW2JK0yp4YmZiTBgZ4Sfz93VvfX2VDUOoS7fpp1qTl73DFZ7fDCnWlGhyD1DseMPmoveawLTuGeOzIGUD/O77fRBSu6JEY0gTDMbwUA7RiqAhlxkF/pvnuyT/LlZIaFpNOaedN3tpFl5i8XFcnPCoHjq4UAb/3A3nSeuwJ7gKaYrAxRp1Nr5S9i57dCBKRNkK3j7MaxSnEaVGYf5rwxU3B4lRFWuVhFJM6ZeThC9oOPgg1oPv8Ngve+81c/i2r7PGpRJKggHl0imY+3wNfr7lqNGEhg9C50cZqwbMFfT9lFOBef/nPs+1nJGE8DTV5GxQPlCuYgF0+Q6j7tBDjQfomLcRXCxc+OokGVWaONyhAeQdtrqZGhqPNxxd6/SBgXO4Fz+F8eiPgctIlnJpVAJ9Yfps3HWdURn/fhmRRmJHWGrqPaHC0LWklrFFB/GDdL+yKUMXTVwePwX+51dQuRQObpUbiL6BcWrWQg/viLJ9eBx4Tf7DhOx6WKP7yh93VaoyG0mm6ffHUNkwgBt5LizqWg3WLOwoiWgwVyx2EImySWc9PXtZOPKkNdLLR5EydgBp9XCZweNz5z9FAI6KKu1sJIUBF4mfV/lcwa2tY7w6iIA5jUMqAPIIHUZynpbwOfmgFTmiNWy7thVAesoUTC67OyskdS5OCFN9ft01sf68Wr5IwbPvWj4ewStvEfYWI5Q3MxBxtrWrsApCXDxC/kf8xRUhBziBrpj5ybvMofrleVw0I9qDRIW5DzoBTE0xO/r4DD+6El2anjNBTX04LCcYG347avDEMpZAdUbDUtZ8ry5LJl2Ri2KqhG09dXR50zlK76Ex6xFMq+TcebC08wTkSwXeumfKpxWyfk9/dxCHKT65TOWIOBVg7NfUxOOtSEyj4lltGR8d8JYHkBeAg23SzoUzet+VZeEYXTPif4/gPCMyWGU4etYTdHFYN/AFmJTlU7/4dzTdDC6Ckbzpcld5YCNUhCn60z2s4iG0JjX61dZx7gQWldNg8zW7jZob9YkiM4t+NnvkfIjqLU2yGJ+YYsxrlLIzvYQk/8/epLP7lPxfyiOiqYsD1Sb04LSfQ+IeHYNWvcvaiHScAA==" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "EFD262F0C8C2624CE15916D24A63757A6598D17DA589309EDE486F74A54C22A3", + "noteCommitment": { + "type": "Buffer", + "data": "base64:KGGVtpXGGYrG8XVYIOBYLFmAhmzjUEyASO4SlGsARWw=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:BKsYHwJvY5Crxr33TbAhqSewl2xX3DR6P5mxrZaJ6ug=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1689367133807, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 5, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAZBYaasyECgxNLjaf/R3F715oG3OGF7QvO3LR8aNpwJy23BVvnfA6i6fccx3nsM4VkLdwMSLCMvcox5PtXxXI5wJtGraOsu2GsjOcIeUUUbatzO50D7cC2Ls3HsC/oDPgJUmpTMSjWaQwU/Y4KIMULUVDEW+PpoGzhVk53DOjr5AAv/L4kLAhtV//JLeqiNW7sUbyWeJR6FMbs6qyEj0u5uqFyMy2MPNXMjt6NljZ2IWhJWCKbK9YvkCPpF/c+UMCCabb2WxHoG5uP4Xuw6gS5a9A9gVzjVd0qKmZKUwEGHUv38cVlVFEPRkDSD3Ud6q+nBfPRfakELu1Qzx66nC9tXXNF3o0a407H7/jXS+4ttSxl+mu58gpC8d3DFND4lltINdNXO3NonZFSIYI66hG/2t8hSSCTcrqZ3tivr3xLJFX7+3DWQzsCvxGTznnapf7gpvGTkZB1HQ47nOUA61p/O4JLxnY3c84ua3dbTPCBiZvNgq9KOzm2Qd72zdIFnpn+Bgth70r0lxVsaAo+kRK0e1ukbySU5IEPd9kwpp4Lccibn4LU0XN/4WzR/syBtydaNH9b+dzOAJOlvLDeWreBiWhvW9Q5HarFNtBP7u43ZUtfw5QsVBMtklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwS2Jk48zV13LcUuDjmYUlRohmjf3w+EyiTw1uM2o4JkhIvTfqD6KELxZzu31wT1hPbsipmCzPA46MuDCrRH+XCQ==" + } + ] + } + ], + "Accounts expireTransactions should only expire transactions one time": [ + { + "version": 2, + "id": "d8fce5fb-47f1-4a50-a5b7-5cac19b47a06", + "name": "accountA", + "spendingKey": "82a6b028c10cf518ea09e1de630b5b97dec48ebab5bea1821898774e024095d9", + "viewKey": "b79e25c958a76d8afeda478edc9203f7392b0ac0c0277c873c23cae5bf20978232e24865b063fa861964e20c7a1d483296570eb987336ba572fde9560da2260b", + "incomingViewKey": "8a22f07c7d78bfd8be594cecbc43d36aef835598f2c33e80576f12d5627b1c02", + "outgoingViewKey": "ffb2480042a35374c0d494b4e13c14de2dda4f5f4b9eea7f4b24167bf2a119fa", + "publicAddress": "65c2ca7e7d0fe753b1d5e99e70e03cd91978410ccc2b3f7173a74978e78a6513", + "createdAt": null + }, + { + "version": 2, + "id": "52390331-196f-42ec-8bb8-36e3e1b51ab4", + "name": "accountB", + "spendingKey": "d531b79fe8ca8e431ed820b1ae13999e3477647587260dad78652ad252e76b77", + "viewKey": "5484f868de52000ba32b4b714f7c4cc0e3b24bc2d969a9a598b6713771734c3c6420ccc867514794131a46ffd7edb4588da97a81d9005bd05f0aa4756f420808", + "incomingViewKey": "3e7db268ef138c79793bd5e0044abcc4874bc69b3fd79b9d700c9604df646301", + "outgoingViewKey": "f14f63843362a0e7867bf6ac33c204220c28a15fe1b81463731e5444bc6785c5", + "publicAddress": "de18f9433f48059048210f65353a679371e6c2cb5ce4e0623ad0168f3e182d4e", + "createdAt": null + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:s6GTSZ/rXcsCrdco2wC80hW+iqUao7APt0biPc5DvTo=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:R2Ge3ns3N9g/m+Nf1PU+eNjO+t3JOEMuKmn3Kqr26aM=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1689367134401, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAmE89ozpSzJTEmQN6iyjCpaudWBQfsQtarAagGlEoCzeiwCVdyvmmpZ8BLMBeDU2ydRh96Coa2e/9d8wfbXDJtOO8/AOST2zeN2qB68bUUEaL+6ixWpAseSyEZ1vV+peKE5EM0LOoUnbXb0IZF+MmpGTwpR0ROyX+mo9kqhiJljgBA31AcGPbWlYt4k5B8pvHxq3oqRBvYai3D2dSl5lhlzEpgRQycY/33DBZ4kuQtWO2SXgzYIbNT0QvutESNVhVTM6zgJPjdcoe70iE+3EgMhFj4VvhRJtB1mAYkNLbzy8Dee2NTyVhBN33F3L82AJlUlb5SIkYMDsBtkA9DDZT0+REvg8dGYhsS9a4QJkoRttmspZFFMkEl1UlSvJxhbtfhwvH1wSgl7JfK2nDp7S/bKWx9ZMJ+YUhxtYWm96oZZFWI+z7Ee8t9NzyBZ/2OxQ/uvclc/Ek2/vhnHSy0LtsTG2b+vVVOc4yFIEr2Ln1HZwtq785u7/L/Z2vLmJymg5yALQ1wt6jvw7EUGIxxboT2d4wkuBYVgaB3jtZMnz2wicgbpxHj2FX7lcvdfFc4qB8FxDI4DsVd5nosLicEyu3TXta2m74isVGogKzIHu/mKis64A4BKioMklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwZXqv8N3IhEWhEjQZsrVK8DD4hD4e0ioHWvIhDs2D97C6Az0HrjqqNv+32cv1exgXicQlp//w0Pa3QM2BNww7Ag==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAcYhviVotFV8PVye0r2hob9vDxuDs7YHxOHIjp/xxDdyNH11Hxgm6bqHSa9+89IiaPvjt1YSEafwGBdu8J3JLN0vOvojtVqBa4z+zoFpSRbyX9JnH7kzXe+8wSGH8gUgwVLd7YhyatHsdGAOk5nJQO7ophKd77l//QeOyNCEHmBUNI5qbSilpaGHRZZLhFW4LjRABgRC91O6UDEew8vwl86To6nLI/t6iYBxuzF9hB66ihQgzOnsoJZ23ch0BLQsY/wVh0cdYKV6WgMNoDonzHJnRqsEYs+D1cSHsYavK0jXeTlDky7gioKHzbW0H+NAxS0CBxwj/PiGLSDXQQ547sLOhk0mf613LAq3XKNsAvNIVvoqlGqOwD7dG4j3OQ706BAAAAFz6uH+HQUzByyNiZZJbkqfAhk8aIyWhg8iTc5cwdUWZxxfQou5lX6YVOLTLXMJD6JZxwnn/RLLso8hIEqC0KpHCXVAXrFTKcwIUzIMl5Hvmy2KPO/AasV8FungaM9pACYVwTwN1DoAtzWdk0xdxI/HuVQZbrO+FTQGEFeFBL6LB6vZ0rpEU+aZQ9MQDBMzFtLhEY/q067uITRHs/1+zu0MLz2VhmRMF2QI57xwChAlaq907x2SzjHsQHbKIevpxUBU88kc1tmeUcBi5NmcSkupBw602oFsljHojpT9Wbiql2QR9eWjUSeUh3OhCiz79JbSxfXVz0w/1+0u7sFOMY+18SlCXjABFEcJiin4trwJJj5ld+kNthTODeviH5yvNXG/YgHgIqwdodJLGDoOo9Uw7hR/TNdvJ1z/dkBLdMxi2bND1+/9eC68h7bKvovVyM61TmG1UPhgLPxRRaZVA0El/3z4MczqfkZnOUuGrQI+54fVccF6VJVAjMu6B6bDKnWnJhkWzf7C3HCVRG4Yv6mdZR02wt79YNxgmHOq6txhQDEMksjiIzUIPOoa1LJ3HfXUfSm2yZaA9zV/ooDs02/2achxUMMgIjTdNq3Us+2RZNPVJCBr7ikeAztU7IbIoV8woyxnDiPJOdelvZLy9XdPPfq3TBVtoJLRLvnS0JbNtVam+0t5zziNNhLDzNd0xGbNQ8so2jjjvWwgPiUlhsCUBZyN8YNFhD2uJ6CpLynLvqyxxo+S1HbGnr42cUlgInC6gEBZrEcfJQc50VPyYPcK5Vlgoi02/qqEy1YrWve2FnGMT5e9U1jWOpb3oib5NL1O9RM93gLaqiqVnyV8/3IeqCLgVR/r7eqfgLlSvfh3RGmhzP6uB8eO3k/7Vr04HcApiSAWZj1hlH4xE84JAtYLlH3frChrNowIPuN8/ggqcLJLkI+obslEDzFzBz9vbxsnFzoKZl9HWU3AUAvsSgbYzOfsMzO4ywbQOYyoJaZLCi3rcxOaPxXWSwHeDxFTa+zEzeMrjFU9rvbbBibp3mt1TWIeWk+pKIPQ+UXVgQ1uB6aWRxv7OmiZN0L9JZhVj1J2Bcp1/LBr0Gy29CvAzl36bmY5Lys+2n9mBSn/Wv1NMOP7Cj4Sg8/kgpmeumeahnvOlvZLnFMQHRdSoMnzmfRjjje8TeLRWS8iqRaQDHIevnlwRRaHxk1y71krImw2gN2EWO7DkkUI0yUDziGtnXnT7JnGasfH1nQEAJNnHY1o65YjJlWpiLtlgfzB59xYM4xeXcFWUx8WBAVkAAfy8moK4Cb9hShumai9veSgmiv0bYHk2ttO0XXzP3fENlEtMkWhoSmfVOyLAsyg29moqNbzsfhhyBRBOoV0bxK4mx2U4VjGxfiiwI+YeVYhC1C5KLoF4GiuHbjFz66ZZ7CmJG+mM82aX58Hq3n2L4aA0fYl6tGKAy7qjxmCs9OvbizhhiC+PphGsZk3qIgJBQXSpTszl3eLtLnDs1/tisxQ4PGYCLzVhz+EwRYnc6FS0xKjvf1cSnWQ1t+N4Od4KJ7BX9yl6+JNm6DNnQH976WjJ3n/vUtzNQG/uDdDjsfJtKkUHBw==" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "31D3EA862371456F0966227A363C07EC4F0A30C15DBCEF376FF42CA03F2ADD7E", + "noteCommitment": { + "type": "Buffer", + "data": "base64:xUQgbWqid9pOxlKfrhu1QCyyX80vMsBw5HTIvBvw42g=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:9XMSawfx6GIjvheLhK5E4bBWp250pITkRIJ/Qnzboiw=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1689367136711, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 5, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAis/3xCpY1nFemKV2ODhmsaW1zTY/TWhPsfUgOPzcgUWsS1gW08eRd5AdcxJ5gZu9G3WA2oIrX27+1kN4XC6FY+aPrMmNgfyavx0SRzpDmta15+EC1yCgJZFwELmyjp611XrK1tULJHBmxHC8KhD7amhieRNpdJ9Z4fgKJXhtJM8XdW9KIQx8ZA62wZyYd3V7T/w76MJDucYlEWtedUZduDKtH0hs7vfUTyffexx8ScSCoXXraqDWkOUS40CfJ0hvsCI/likBfUp0XpmrzLfUHbARo8H5KFovJqULAz2cdWmybLwpk1CfLPVr/9fVGYJtJCcIh04FJ2xiiStCkhujT87LbLlhDWLrBV52hQdUIMrqHiwC8nsuEwznhI/E8mQ+YXfm1p28vLCZ7q3ZdDhLiekvSSKGR6gB/2rIvh8wym8gYXj0EfD9YiYeV/g80k7vnV5Fn+e3ZMlBdopRq660dhg2b2w3RBLQ/Q9Yf4EpvCy2nuyFO2Gs3pWPXErKmo0l4FFyWVmWls3Nqvo3F1MuOdy2qV7VG8gc0I2Bsxi0seBWSZsMTM8TWkedUAMlzjAMGFa8GKpYAfiubOg8F/9B4Al3XMRH5+9YkqS1yCF2iLIE2RWmX7knG0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwakuFMRj2t3xmS4Thb5Rw1aKgRcIKmFu15DBjLk+pxMK3x+UeEGPq9v4h0wFr1awqP3JZ0QpF0TLNUoBdI4rXBA==" + } + ] + } + ], + "Accounts expireTransactions should not expire transactions with expiration sequence ahead of the chain": [ + { + "version": 2, + "id": "2f472eb6-5ea7-4cdc-a39a-ebf76ec6e6ca", + "name": "accountA", + "spendingKey": "c3b1531aa8b036ab3416011747cd675866a53855d0b502d8b856916fde5a8c07", + "viewKey": "60b5b73ae1454deb89d49387d079ea6f9932146dc7ecb601868950b9b427dc552117ee76c20158ebf56cbd2ab1234fa9b30f3fe74f9097950d3099537cd5b442", + "incomingViewKey": "e11d4b2647b2ee6210586781b923798e795446699b88e5a727f2e228ea6f5b04", + "outgoingViewKey": "a143d87c411f53e5546c671920c945a7dce8fd13390b55b1e007311f7736e95d", + "publicAddress": "c0dcb30e0b0f95b90cc08ec599778cf2a9bd0f3eb50039e580fd9ab7a6eb6cd1", + "createdAt": null + }, + { + "version": 2, + "id": "48b81119-e6a7-47d4-823f-f4915479471c", + "name": "accountB", + "spendingKey": "43f2425160ebfffa0387c7e29d8e4416970a649f6315d762f3b5f99b59e1070b", + "viewKey": "10f26da4819350cca7bb9675bd5da3799fe24c60b6882e9df7ff11efcd347b94aa46be9150da57b3cda44e9d5e278611fb5d9f219e5a8bcfcbd48eb7255542e7", + "incomingViewKey": "15640973adf4a4ca26d5366b51b96ac48f09c357eaea1511bd5b3a172cf25804", + "outgoingViewKey": "47042ee9ad11f9a6e1f361cf388289486915872bcc3143a25d177f7365cc33c3", + "publicAddress": "5dc3a6d3afeadd351455072054c37e6d0f688215deed21f85a8e3e627063f2d2", + "createdAt": null + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:jIemDnHLoP8Yz2biTolIHDLzvjkHjNruZhZTxKoM9lA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:WKHZNPyldcHvwBc3iuc7HDt2wcQ/U2G5obL498wDmzg=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1689368844053, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAxhDzmHRvzJfvGgeGrY3iz2AnNpJWoJgrbma7aI7OVDuvUWYStZ+RpJDw9AeNOMnKTSNYDBbmRBqG83PI/X5KeFRecFpvkI3iGRHake5Obs+inxViLg631JYlkcKNNXIyTaW3/COITYiflJgy8BPWQX5IVFSgZ1CbpUE8mg5c4O8QMQBSXM1mhRsAyL96DQDAW+KLUHFgSzZWUaavkIPB9JvpYwORHCeBcmvHdHgrfkKrrlTCt9FutaDlqdWCcWs7brEmwuzh5JaUuU3FnKV3WVb7IoGVb0fdLGoVdSGy5p53MI+O86HPB8lMA9fCuh3Muu/ZaWiaHVTq8YYtZchW6QbKV48lihMjAuNamjVWk/KwdKp+5Mz/wIGsj2UIazgFmNkvXZK8Z/tAdUZwZ/W5yU+Zob/WDWWUC3tDUJ/eIQtU849IAducB+aoNqu9AHLrRz3Ma34Cd53SZvyOLsmEn2bpi0nbtU0+ybqVezGwyqatRbFYXzyse4/0ERMI4KC7dllUMKkgL4+YFUoSQEIUFgISQM99ObDb3xVJCccfJ3OlNVSRyAuyG2CuhJHZOEoa253tePEDfDhz3zF1f8OCmv3UaoigKrHYYgjDBzzZJgc5b08l9gr0hklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwuHp1N1W9tlMGRaJ5L7Z+WkYruTC5cmwX8QkRLT4wKWGQAp3KrynU7V3Yw+OOmPM4fAXbSO6AP3LosCUMd6ENAQ==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAA2mNmFmAhbk2eMZdxDvneomsezIaGtzMSMgzeIX11Y2CunooovVo+UG1thfcSy3mDD+HM6aN3i0pJdnzkgmv6R9TwaaEvWmN8UttRpt9upqCJ4kE1y6gWKj5JFyjvwc9fOGJQQwZpp5KDMr2W+24gCWg/uoS8Ei27CZ8Hsx7/1lANrWey8scTniQ30FohlBvwCGLB6Dv+COrfWRnQKAN+UkSmvRzhpIBJ0S6iY4ukMBiThR4eVyGLAux1TyafUtr0HmtzBPXxPkvDKVt/q3bOfrMTdblne2mLL1iXoTzWgvn8fidKpYtD64SbVTszQuDc50FRwtovUPCX5Xx3leSPsIyHpg5xy6D/GM9m4k6JSBwy8745B4za7mYWU8SqDPZQBAAAANw9g6/lS9b/eTNXHxbdvuec16twOKb+ATx9K/ANzvktXq/03JMZUJtw3iXcKHqT3/h3Es1x+TA6Y0Wa3GXxrgOBKA102ozi/eeRhj+K8R4sUeg805k2M2AuBxKv9zxWBYaiXqDmH0B9kOtM0ayCCbbZGR0u3AkEE43ociI2nkgX3IwLCeI7qItQQEXt7f+396p4TnH9YRaQdiAlLXns5PqXPV7x4H3DyCZZm8rF7KK2e0YsadSLp9M7qv7453L5PxWWgjwqUCnAZuK6EWPXQgTuh0h3PpSYaEPI3H/9l4GOyAlrOZr87ZDtXN+VTz73CqUhNh9nl4tRKuu5gSfuW/nBOL18UvUVprPag3I6YVBSBhe4gItb8FisVHlTKrSM9OT9acelNHxKiCU50glI7G1I2Rsa4sMVxcR6iUyMWnHFyDdr8Mt+RFHajnOD7l3/ackOSRsFuWGM8yta/un+C191ub/IrPT9HvmCBbjWG30yL3CEGqqEdywPRK9WB9GcUaC4nC7F/lT5/gMn+A1C6jKx+xRAWhe163HOs4FJGlCw6kne0Kr6T2vjPL6yfoMl/jrfcz6IRjaiydzj8MiMIxaqG1Gmhty2ic/3SBI3LueyxNX/eiAvVwXkYjvKB1lgCDINE1pP1HcDluM4lEua8e6Rwq1LaI6n059i4xuHS7sOPeb6HfF199fAwo28Hbl/qbyhhFLTfi8vz5CpcsEp+ON2UhnltRLb8vXCE0ASL5pk3wFCdeeEVah98oTmcgGvZuXU8PBVgWvDUyuu9raq9apxQ1AIWeWdxMfyGZQKKl21wnbaOzUmJzGW8D7gC2sL+A6NjXFq3GBCAu0UL1msk+tjjc1d72y6qkGg0TCf9UFcTFF4ia81xVOQLOY1ggasK8oNp2LwXApdkL2PQXYjG+AtIt6VlrskiZ1HSZyIXBVW3es9xHPROy4Fzo+qvJ/EUcVL/RAQGh5jNZeD36/c+2xud4aJdiBU1qy+6QBy/O52XZgppEWpSeGTu/QOop0oRj+Gup7n3t0yq7Xw8AykhVgoSPwJ5a9W8jgdkUXsPDaneRjqtQYOcF2ftqkQAsXKncgr4949F5nlIzMWQ+B/jlIPzJNSBHvzqyRLo0cH9jFDRUXBX7LCKypC+8pomEQWdvMyGySUUqo6nwwH2hvmdkgShpjk9qRBTsXqA5z2O/6U5eIDU5lENIz/P9DU0mHgupzZIkBrv1HSf2u+Jk+SNFhXsJWxxWi0chDdEvFjYprkZmbzXpSsLwzbZhSn5pGah3P8fMCxrj349f+UiOOt188sez/4CEEByeJucP5ZBIiTtG5jvFP1I5FZc6hO1ZyFaghZslOAlGBooO8rqBim44QyqWq6XmWhf21ZICWHAkY1DC5Ny0BWc2xEYdBTF98DC4KnTHUCJgdeJq9xQpZqkXo6V8fbOkO346fF8mmBkb7dIY5Mp5hgrW75kok4fjVPKKeQ25pVMis59JAu9TqvML9q62gWkdDeRQmpWBSdvtH1SSFsBK0nlBjdlnSF6oXOr4i1jPlwHpdJndJmmM0gObpYRJX6fsUsHer3UEQsDp1DKNWOJr9SXXIwFe9JYwSnAA==" + } + ], + "Accounts expireTransactions should not expire transactions with expiration sequence of 0": [ + { + "version": 2, + "id": "5d384965-d0e2-4a13-a957-46062aa7537d", + "name": "accountA", + "spendingKey": "e3d8ea8c6ef15195a332d169156cdb0ffc65a6c028207831c02bf6332dc67edb", + "viewKey": "5459c9b307b402bd1028b79ac8503d52443a125488241bc9aa32ca5a3716f2e0797d9cc082ffdb30c4fb10a194db35fefb4e5decce1cdeebb249c315ddaabdad", + "incomingViewKey": "e11a480115b02124ac67ee17266dadc1cc32b215a76636896673ec7eb553f906", + "outgoingViewKey": "da245dc249967f0d698922a3471a3b06364df387198333605043af93323a67aa", + "publicAddress": "0973622d35d830c86d619612516e70daea57ae2465cf8642b3607c609651bdc4", + "createdAt": null + }, + { + "version": 2, + "id": "b7063a09-4b46-4fce-aee4-af431a557d86", + "name": "accountB", + "spendingKey": "4dff73ae13afdc502ddaf28e086ef5f71a990a0b1b08e2885379be0196d430df", + "viewKey": "c3f39f954e2c2a70ce9a3e9b84899a1d7f49d1314476bb6bf6cbee53005a198958718224c4ce3ba0a294ab8d748f7daad49f1ed5ad3cc4332566a2233f777ddd", + "incomingViewKey": "3b0942b573e4d1af750aac1f963961cab061784c22205e458b4f588d8762cd06", + "outgoingViewKey": "6737efe63c633d4bc4d484824537f15b4385676063d28147e9ddf08777b45a0c", + "publicAddress": "606e63d8f6d2cf0ad35f311251525a83627af4faa02ffeb5c33d7c9a0d3b4c46", + "createdAt": null + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "88B6FA8D745A4E53BDA001318E60B04EE2E4EE06A38095688D58049CB6F15ACA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:IFyyQHj5/w5CTq28axFEE4KE+vtKZ3FuqTEtUwwLgRk=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:vSaLevBNyxHqEYV13xsa8BrGo0i2tebEHZfq9FYFjDY=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1689369137743, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA/Hrch3WUphDZ6Kh/drZubauBzxO7S8wCd6uzvT/xUT2XL9s0pTwlSzcQvSZuHHyZhIEjK18+jICOGlb7Yf4wXbZHopF3rpvwoE9SN7OOIz2XwGsljwkEz7sN/X0iyckNrdCo49TwJzJgHzS2tiAs+ytXIFH1vnGpHZxDfCxQFGcQG7UMgjg6+JBOW20U5A702UZF4wtVX5E4qZV/ELx2L4Xr6F3Cz5C7VNTpzioAkbeksEbamAXSzg6i8z3Br9PsKp3Qyny/XptygoPGR0ea1C0dx7USsh6Fn4pX9EWVy5zey+sqMDh1sgY3LNf3BnM27zKBAFUfPGwwl2quRzn6j6r1P8iI8nER0XpaXtqeI3MOqjjc8nlLsmNk5oG39rllQKsWqw8raktWdZnS6zYM1rvOFrhBhcpjlwQb0YBmETZnKfD5hlo8+noFGqHV0zuKnAU8MkFdVT7TPKtCAjYW4bbNYzFWlRVbKPnAxA/F4yb4gZL6dQ1dMS0VlyDqdXNwea6UchWMrygWhnvz3clPokCkxF7LZvq78qWJ2y/KVen+TRiv9EnWWDi3CVEFPAYorJ4CvQsh6mlAmnMYZkQM86FzIin5QED9nL0xQMYXTSB5Gyr3DK2KdUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw2VoRIfiFqCyGju5MDbxjeufZ56EBVVBHQh8+QhOqnHBN+Y6kGhaYMBLD0uX0ILcHJlNwVVFRropJcYzRefTWAQ==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1jhbJC/GS87scouPjU2BUYOtaGz4mc4/EuP8m3E4DMq5G2M3OlMfGlUFh/+LdV9KmQHtEdZcWCR7gikv8YLya2u+SUYxCPftb/Ztn4gXzrSM6Kl1IlNc1Cvf//ueraaZROE/z0iQDYvwAOwBgUhsplZSxrwIWTvX8yS7OdRSZ+4H/6iVb9+FT9iEDaGJCpE1aR2s+boMVEusxjkPJvMr/JJmsuCltmXwRCtSsL86+h6Ue4VvlvF7zWYU3nNZldRIsrtvFXYnL64s3DFXfcJ9yR7DJiK0o5KEPxQQT1NSq9IRwqtP8Z/AOgsN/BlfvIQWiG0cjyqI0bKJleMAhydkTCBcskB4+f8OQk6tvGsRRBOChPr7SmdxbqkxLVMMC4EZBAAAAO0EpQftUrY/iiX7c4rOsLpl4qlhNJmICy+q0pGxczk7CuGG9IfQpRR9P5HSXkPOK1Z8ljNnjG7jx1dV4n4wVZketLcYU7x+u2puBwwXG6asGLSgBQPf3xeTZPwXnTGIBLQ7JbWcDmQ5QPVC5pnytxs86kdfCi3sldKex6JZmCQfl2XgzSHncFqwt39k35wS9665xROAt+b+G6PoNlWdkcsyzY5tTin7MHdxX4t1QnH3HEo141GU/noky0dsIhN8UAB6sazhxJDd0GlPN66SUmt9l44THxtsZK16j5X9UJg3+m2lp01znYxRY7OJy8GG/bY/QdEzA1eR3F8TWcsV3hAJ5hTw3b41QXQ9EkeNI4bMmJ+SdLIVpGav/o5QK6fgEv68GFPO0S92mJhI/WMkzvR9m09p0GW7JpLnHge5PxVulnVpUcu9TImzTiNHe65YrO5L5NX/5mZhiYSBEGANMCRk5fl1BiXW1a5miCf89n4ToxM5TC6uOLBnhBZfcxpvAU7NYh2lMZpPNdv46J/z5PQO0vp+XuU6j+TEs0qCaAb0BvNpWiMVppYlzg86gq36OrqTVoukrTvKTeBnbyHaFcuSlObc6o9RuQgv/KIb3ROXRsaQVv0VHv2AkKTfwcNL6P/3peCnLJxvmikTrkMuLAbjjXCFp/eK2QDtazo7nkCOFbfpWDBxcdgD6LIg3hLXfAMdTMxjnuTAXQ2qh6SgZlnjPkCQ03ZBJxzI3/J9v90sh5SDHGRbqGTNh7RJVwvhPYrRhBZGroNbAbw1nsZjU02kdWw2hDUyHe5UOs3r3GgWHk6D5PlwfEOqdyNey6mX6khLYfZRM6dLBmQFFSisAqffHHnpYd3UBVmPld4mANrH6Aa+SWecTPG4XhdmE2+FRVP+J5WLN/FSj/kPT6h1hj8PyiV92G0jRr6y+Ua+ONQm2YNs1aUAxdQUoSMavYMXrvBJ/vFDbl1Q/AEbDqa8FrTuAZ785CD/nbDPWh0vClo6DLxfr/pyilOJ04b6YYE5HraiTova6BFz2JyXqg6C7HMKA4XZIMVINk5AK2gn84it4vZA6OVY3RM3yBoZnW5d7Nl701MiOE4MrDFSS3BAT6LQdnmVDfmZQ7iIwpYX4WxWA7NGOKuDd9xz6rDXTcrwE0gP254BaaNjrduWYO5VDZlbRsBULYaVUO87JC5D0m+8m4hRBn2v2kWnLYqrytdIj2dwbd73l8A7N8I2LtXsjXp7A0WwjQXNo6y5b0Q0GoQKy7TBcU+9AwLMpPZHTvqod6KyMT/ih9kIPY4M+2Qkoh8CaArXhBvvYbZwdSYsFnS1v70l7cfFHVsU+oTlbB6CwMRBjPwxrNXKAxg61bMA72XxMXT9XsOc1S2kgN5LFYHmFvTtut/Wq2fcZR9jFRk3Yu9CR8xAJo3CpMRQcNDCvo304LxyBefQlvxFT6Hf39cDF7b863lQ1U7yLNBlovWT1HlgKJS0mjJAh2jULoftd/9btJWf4mkzcE+rEYR04xg7v/3+dZ4knjbCnSt5zlEfVN+b6CePUnOdI4bM0grqkNfxn/LQftVSalgfEfZyyFe+daAwvbPjPxTjrVSJfL02AA==" + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/wallet.test.slow.ts b/ironfish/src/wallet/wallet.test.slow.ts index 8c519283c8..0fccf7e2a7 100644 --- a/ironfish/src/wallet/wallet.test.slow.ts +++ b/ironfish/src/wallet/wallet.test.slow.ts @@ -373,7 +373,7 @@ describe('Accounts', () => { }) // Expiring transactions should not yet remove the transaction - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(node.chain.head.sequence) await expect(node.wallet.getBalance(account, Asset.nativeId())).resolves.toMatchObject({ confirmed: BigInt(2000000000), unconfirmed: BigInt(2000000000), @@ -390,8 +390,7 @@ describe('Accounts', () => { expect(addResult2.isAdded).toBeTruthy() // Expiring transactions should now remove the transaction - await node.wallet.updateHead() - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(newBlock2.header.sequence) await expect(node.wallet.getBalance(account, Asset.nativeId())).resolves.toMatchObject({ confirmed: BigInt(2000000000), unconfirmed: BigInt(2000000000), @@ -456,7 +455,7 @@ describe('Accounts', () => { await expect(account.hasPendingTransaction(transaction.hash())).resolves.toBeTruthy() // Expiring transactions should not yet remove the transaction - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(node.chain.head.sequence) await expect(account.hasPendingTransaction(transaction.hash())).resolves.toBeTruthy() await node.wallet.close() @@ -473,8 +472,7 @@ describe('Accounts', () => { expect(addResult2.isAdded).toBeTruthy() // Expiring transactions should now remove the transaction - await node.wallet.updateHead() - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(newBlock2.header.sequence) await expect(account.hasPendingTransaction(transaction.hash())).resolves.toBeFalsy() }, 600000) diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index a4cf94c664..83663e7ba7 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -705,7 +705,7 @@ describe('Accounts', () => { const expireSpy = jest.spyOn(accountA, 'expireTransaction') - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(block1.header.sequence) expect(expireSpy).toHaveBeenCalledTimes(0) }) @@ -726,7 +726,7 @@ describe('Accounts', () => { const expireSpy = jest.spyOn(accountA, 'expireTransaction') - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(block1.header.sequence) expect(expireSpy).toHaveBeenCalledTimes(0) }) @@ -747,31 +747,28 @@ describe('Accounts', () => { const tx = await useTxFixture(node.wallet, accountA, accountB, undefined, undefined, 3) const block3 = await useMinerBlockFixture(node.chain, 3, accountA, node.wallet) - await node.chain.addBlock(block3) await accountA.getTransaction(tx.hash()) - await node.wallet.updateHead() - let expiredA = await AsyncUtils.materialize( - accountA.getExpiredTransactions(node.chain.head.sequence), + accountA.getExpiredTransactions(block3.header.sequence), ) expect(expiredA.length).toEqual(1) let expiredB = await AsyncUtils.materialize( - accountB.getExpiredTransactions(node.chain.head.sequence), + accountB.getExpiredTransactions(block3.header.sequence), ) expect(expiredB.length).toEqual(1) - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(block3.header.sequence) expiredA = await AsyncUtils.materialize( - accountA.getExpiredTransactions(node.chain.head.sequence), + accountA.getExpiredTransactions(block3.header.sequence), ) expect(expiredA.length).toEqual(0) expiredB = await AsyncUtils.materialize( - accountB.getExpiredTransactions(node.chain.head.sequence), + accountB.getExpiredTransactions(block3.header.sequence), ) expect(expiredB.length).toEqual(0) }) @@ -799,22 +796,19 @@ describe('Accounts', () => { ) const block3 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) - await node.chain.addBlock(block3) - await node.wallet.updateHead() - - const expireSpy = jest.spyOn(accountA, 'expireTransaction') + const expireTransactionSpy = jest.spyOn(accountA, 'expireTransaction') - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(block3.header.sequence) - expect(expireSpy).toHaveBeenCalledTimes(1) - expect(expireSpy).toHaveBeenCalledWith(transaction) + expect(expireTransactionSpy).toHaveBeenCalledTimes(1) + expect(expireTransactionSpy).toHaveBeenCalledWith(transaction) - expireSpy.mockClear() + expireTransactionSpy.mockClear() - await node.wallet.expireTransactions() + await node.wallet.expireTransactions(block3.header.sequence) - expect(expireSpy).toHaveBeenCalledTimes(0) + expect(expireTransactionSpy).toHaveBeenCalledTimes(0) }) }) diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index f8482a3b10..54caa648fc 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -130,6 +130,7 @@ export class Wallet { this.logger.debug(`AccountHead ADD: ${Number(header.sequence) - 1} => ${header.sequence}`) await this.connectBlock(header) + await this.expireTransactions(header.sequence) }) this.chainProcessor.onRemove.on(async (header) => { @@ -293,7 +294,6 @@ export class Wallet { this.eventLoopResolve = resolve await this.updateHead() - await this.expireTransactions() await this.rebroadcastTransactions() await this.cleanupDeletedAccounts() @@ -1206,31 +1206,13 @@ export class Wallet { } } - async expireTransactions(): Promise { - if (!this.isStarted) { - return - } - - if (!this.chain.synced) { - return - } - - if (this.chainProcessor.hash === null) { - return - } - - const head = await this.chain.getHeader(this.chainProcessor.hash) - - if (head === null) { - return - } - + async expireTransactions(sequence: number): Promise { for (const account of this.accounts.values()) { if (this.eventLoopAbortController.signal.aborted) { return } - for await (const { transaction } of account.getExpiredTransactions(head.sequence)) { + for await (const { transaction } of account.getExpiredTransactions(sequence)) { if (this.eventLoopAbortController.signal.aborted) { return } From 56671ef7a137c173f97ed451185260b15c4d94a6 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Mon, 17 Jul 2023 11:21:40 -0700 Subject: [PATCH 33/37] Separate global routes from Router (#4071) --- ironfish/src/rpc/adapters/ipcAdapter.test.ts | 10 +-- ironfish/src/rpc/adapters/tcpAdapter.test.ts | 14 ++-- ironfish/src/rpc/clients/memoryClient.test.ts | 2 +- .../rpc/routes/chain/broadcastTransaction.ts | 4 +- .../src/rpc/routes/chain/estimateFeeRate.ts | 4 +- .../src/rpc/routes/chain/estimateFeeRates.ts | 4 +- .../src/rpc/routes/chain/exportChainStream.ts | 4 +- .../src/rpc/routes/chain/followChainStream.ts | 4 +- ironfish/src/rpc/routes/chain/getAsset.ts | 4 +- ironfish/src/rpc/routes/chain/getBlock.ts | 4 +- ironfish/src/rpc/routes/chain/getChainInfo.ts | 4 +- .../routes/chain/getConsensusParameters.ts | 4 +- .../src/rpc/routes/chain/getDifficulty.ts | 4 +- .../rpc/routes/chain/getNetworkHashPower.ts | 4 +- .../src/rpc/routes/chain/getNetworkInfo.ts | 4 +- .../src/rpc/routes/chain/getNoteWitness.ts | 4 +- .../src/rpc/routes/chain/getTransaction.ts | 4 +- .../rpc/routes/chain/getTransactionStream.ts | 4 +- .../rpc/routes/chain/isValidPublicAddress.ts | 4 +- ironfish/src/rpc/routes/chain/showChain.ts | 4 +- ironfish/src/rpc/routes/config/getConfig.ts | 4 +- ironfish/src/rpc/routes/config/setConfig.ts | 4 +- ironfish/src/rpc/routes/config/unsetConfig.ts | 4 +- .../src/rpc/routes/config/uploadConfig.ts | 4 +- ironfish/src/rpc/routes/event/onGossip.ts | 4 +- .../src/rpc/routes/event/onReorganizeChain.ts | 4 +- .../rpc/routes/event/onTransactionGossip.ts | 4 +- ironfish/src/rpc/routes/faucet/getFunds.ts | 4 +- ironfish/src/rpc/routes/mempool/getStatus.ts | 4 +- .../src/rpc/routes/mempool/getTransactions.ts | 4 +- .../rpc/routes/miner/blockTemplateStream.ts | 4 +- ironfish/src/rpc/routes/miner/submitBlock.ts | 4 +- ironfish/src/rpc/routes/node/getLogStream.ts | 4 +- ironfish/src/rpc/routes/node/getStatus.ts | 4 +- ironfish/src/rpc/routes/node/stopNode.ts | 4 +- ironfish/src/rpc/routes/peer/addPeer.ts | 4 +- .../src/rpc/routes/peer/getBannedPeers.ts | 4 +- ironfish/src/rpc/routes/peer/getPeer.ts | 4 +- .../src/rpc/routes/peer/getPeerMessages.ts | 4 +- ironfish/src/rpc/routes/peer/getPeers.ts | 4 +- ironfish/src/rpc/routes/router.test.ts | 4 +- ironfish/src/rpc/routes/router.ts | 84 +++++++++++-------- ironfish/src/rpc/routes/rpc/getStatus.ts | 4 +- .../src/rpc/routes/wallet/addTransaction.ts | 4 +- ironfish/src/rpc/routes/wallet/burnAsset.ts | 4 +- ironfish/src/rpc/routes/wallet/create.ts | 4 +- .../rpc/routes/wallet/createTransaction.ts | 4 +- .../src/rpc/routes/wallet/exportAccount.ts | 4 +- .../routes/wallet/getAccountNotesStream.ts | 4 +- .../routes/wallet/getAccountTransaction.ts | 4 +- .../routes/wallet/getAccountTransactions.ts | 4 +- ironfish/src/rpc/routes/wallet/getAccounts.ts | 4 +- .../rpc/routes/wallet/getAccountsStatus.ts | 4 +- ironfish/src/rpc/routes/wallet/getAssets.ts | 4 +- ironfish/src/rpc/routes/wallet/getBalance.ts | 4 +- ironfish/src/rpc/routes/wallet/getBalances.ts | 4 +- .../rpc/routes/wallet/getDefaultAccount.ts | 4 +- ironfish/src/rpc/routes/wallet/getNotes.ts | 4 +- .../src/rpc/routes/wallet/getPublicKey.ts | 4 +- .../src/rpc/routes/wallet/importAccount.ts | 4 +- ironfish/src/rpc/routes/wallet/mintAsset.ts | 4 +- .../src/rpc/routes/wallet/postTransaction.ts | 4 +- ironfish/src/rpc/routes/wallet/remove.ts | 4 +- ironfish/src/rpc/routes/wallet/rename.ts | 4 +- .../src/rpc/routes/wallet/rescanAccount.ts | 4 +- .../src/rpc/routes/wallet/sendTransaction.ts | 4 +- ironfish/src/rpc/routes/wallet/use.ts | 4 +- ironfish/src/rpc/routes/worker/getStatus.ts | 4 +- ironfish/src/rpc/server.ts | 7 +- 69 files changed, 193 insertions(+), 180 deletions(-) diff --git a/ironfish/src/rpc/adapters/ipcAdapter.test.ts b/ironfish/src/rpc/adapters/ipcAdapter.test.ts index 7fbce38a8d..3a580193b3 100644 --- a/ironfish/src/rpc/adapters/ipcAdapter.test.ts +++ b/ironfish/src/rpc/adapters/ipcAdapter.test.ts @@ -57,7 +57,7 @@ describe('IpcAdapter', () => { }) it('should send and receive message', async () => { - ipc.router?.register('foo/bar', yup.string(), (request) => { + ipc.router?.routes.register('foo/bar', yup.string(), (request) => { request.end(request.data) }) @@ -69,7 +69,7 @@ describe('IpcAdapter', () => { }) it('should stream message', async () => { - ipc.router?.register('foo/bar', yup.object({}), (request) => { + ipc.router?.routes.register('foo/bar', yup.object({}), (request) => { request.stream('hello 1') request.stream('hello 2') request.end() @@ -89,7 +89,7 @@ describe('IpcAdapter', () => { it('should not crash on disconnect while streaming', async () => { const [waitPromise, waitResolve] = PromiseUtils.split() - ipc.router?.register('foo/bar', yup.object({}), async () => { + ipc.router?.routes.register('foo/bar', yup.object({}), async () => { await waitPromise }) @@ -106,7 +106,7 @@ describe('IpcAdapter', () => { }) it('should handle errors', async () => { - ipc.router?.register('foo/bar', yup.object({}), () => { + ipc.router?.routes.register('foo/bar', yup.object({}), () => { throw new ValidationError('hello error', 402, 'hello-error' as ERROR_CODES) }) @@ -129,7 +129,7 @@ describe('IpcAdapter', () => { // But send this instead const body = undefined - ipc.router?.register('foo/bar', schema, (res) => res.end()) + ipc.router?.routes.register('foo/bar', schema, (res) => res.end()) await ipc.start() await client.connect() diff --git a/ironfish/src/rpc/adapters/tcpAdapter.test.ts b/ironfish/src/rpc/adapters/tcpAdapter.test.ts index b305ce5777..d78e3f69c3 100644 --- a/ironfish/src/rpc/adapters/tcpAdapter.test.ts +++ b/ironfish/src/rpc/adapters/tcpAdapter.test.ts @@ -64,7 +64,7 @@ describe('TcpAdapter', () => { Assert.isNotUndefined(tcp) Assert.isNotNull(tcp.router) - tcp.router.register('foo/bar', yup.string(), (request) => { + tcp.router.routes.register('foo/bar', yup.string(), (request) => { request.end(request.data) }) @@ -79,7 +79,7 @@ describe('TcpAdapter', () => { Assert.isNotUndefined(tcp) Assert.isNotNull(tcp?.router) - tcp.router.register('foo/bar', yup.object({}), (request) => { + tcp.router.routes.register('foo/bar', yup.object({}), (request) => { request.stream('hello 1') request.stream('hello 2') request.end() @@ -100,7 +100,7 @@ describe('TcpAdapter', () => { Assert.isNotUndefined(tcp) Assert.isNotNull(tcp?.router) - tcp.router.register('foo/bar', yup.object({}), () => { + tcp.router.routes.register('foo/bar', yup.object({}), () => { throw new ValidationError('hello error', 402, 'hello-error' as ERROR_CODES) }) @@ -126,7 +126,7 @@ describe('TcpAdapter', () => { // But send this instead const body = undefined - tcp.router.register('foo/bar', schema, (res) => res.end()) + tcp.router.routes.register('foo/bar', schema, (res) => res.end()) client = new RpcTcpClient('localhost', 0) await client.connect() @@ -147,7 +147,7 @@ describe('TcpAdapter', () => { Assert.isNotNull(tcp.router) - tcp.router.register('foo/bar', yup.string(), (request) => { + tcp.router.routes.register('foo/bar', yup.string(), (request) => { request.end(request.data) }) @@ -164,7 +164,7 @@ describe('TcpAdapter', () => { Assert.isNotNull(tcp.router) - tcp.router.register('foo/bar', yup.string(), (request) => { + tcp.router.routes.register('foo/bar', yup.string(), (request) => { request.end(request.data) }) @@ -188,7 +188,7 @@ describe('TcpAdapter', () => { Assert.isNotNull(tcp.router) - tcp.router.register('foo/bar', yup.string(), (request) => { + tcp.router.routes.register('foo/bar', yup.string(), (request) => { request.end(request.data) }) diff --git a/ironfish/src/rpc/clients/memoryClient.test.ts b/ironfish/src/rpc/clients/memoryClient.test.ts index dbc1761e9b..3ffe4d1a82 100644 --- a/ironfish/src/rpc/clients/memoryClient.test.ts +++ b/ironfish/src/rpc/clients/memoryClient.test.ts @@ -21,7 +21,7 @@ describe('MemoryClient', () => { const client = new RpcMemoryClient(createRootLogger(), await sdk.node()) const allowedNamespaces = ALL_API_NAMESPACES - const loadedNamespaces = [...(client.router?.routes.keys() || [])] + const loadedNamespaces = [...(client.router?.routes.routes.keys() || [])] expect([...allowedNamespaces.values()].sort()).toMatchObject(loadedNamespaces.sort()) }) }) diff --git a/ironfish/src/rpc/routes/chain/broadcastTransaction.ts b/ironfish/src/rpc/routes/chain/broadcastTransaction.ts index 09832d60cf..a3f732d096 100644 --- a/ironfish/src/rpc/routes/chain/broadcastTransaction.ts +++ b/ironfish/src/rpc/routes/chain/broadcastTransaction.ts @@ -6,7 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { Transaction } from '../../../primitives' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type BroadcastTransactionRequest = { transaction: string @@ -30,7 +30,7 @@ export const BroadcastTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/broadcastTransaction`, BroadcastTransactionRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/chain/estimateFeeRate.ts b/ironfish/src/rpc/routes/chain/estimateFeeRate.ts index cf0dff184e..224c3a6bb4 100644 --- a/ironfish/src/rpc/routes/chain/estimateFeeRate.ts +++ b/ironfish/src/rpc/routes/chain/estimateFeeRate.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { PRIORITY_LEVELS, PriorityLevel } from '../../../memPool/feeEstimator' import { CurrencyUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type EstimateFeeRateRequest = { priority?: PriorityLevel } | undefined @@ -25,7 +25,7 @@ export const EstimateFeeRateResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/estimateFeeRate`, EstimateFeeRateRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/chain/estimateFeeRates.ts b/ironfish/src/rpc/routes/chain/estimateFeeRates.ts index fa9a79ed0c..8ea71d96d5 100644 --- a/ironfish/src/rpc/routes/chain/estimateFeeRates.ts +++ b/ironfish/src/rpc/routes/chain/estimateFeeRates.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type EstimateFeeRatesRequest = undefined export type EstimateFeeRatesResponse = { @@ -25,7 +25,7 @@ export const EstimateFeeRatesResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/estimateFeeRates`, EstimateFeeRatesRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/chain/exportChainStream.ts b/ironfish/src/rpc/routes/chain/exportChainStream.ts index dade9539d8..078d529d98 100644 --- a/ironfish/src/rpc/routes/chain/exportChainStream.ts +++ b/ironfish/src/rpc/routes/chain/exportChainStream.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { BlockchainUtils } from '../../../utils/blockchain' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type ExportChainStreamRequest = | { @@ -58,7 +58,7 @@ export const ExportChainStreamResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/exportChainStream`, ExportChainStreamRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/followChainStream.ts b/ironfish/src/rpc/routes/chain/followChainStream.ts index 0b9756a99b..94e24aa2d5 100644 --- a/ironfish/src/rpc/routes/chain/followChainStream.ts +++ b/ironfish/src/rpc/routes/chain/followChainStream.ts @@ -8,7 +8,7 @@ import { getBlockSize, getTransactionSize } from '../../../network/utils/seriali import { Block, BlockHeader } from '../../../primitives' import { BlockHashSerdeInstance } from '../../../serde' import { BufferUtils, PromiseUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type FollowChainStreamRequest = | { @@ -136,7 +136,7 @@ export const FollowChainStreamResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/followChainStream`, FollowChainStreamRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getAsset.ts b/ironfish/src/rpc/routes/chain/getAsset.ts index bc5d1262b4..34ceed3b08 100644 --- a/ironfish/src/rpc/routes/chain/getAsset.ts +++ b/ironfish/src/rpc/routes/chain/getAsset.ts @@ -6,7 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils } from '../../../utils' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetAssetRequest = { id: string @@ -39,7 +39,7 @@ export const GetAssetResponse: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.chain}/getAsset`, GetAssetRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getBlock.ts b/ironfish/src/rpc/routes/chain/getBlock.ts index 8fb7c881a1..8cb0725c57 100644 --- a/ironfish/src/rpc/routes/chain/getBlock.ts +++ b/ironfish/src/rpc/routes/chain/getBlock.ts @@ -7,7 +7,7 @@ import { BlockHeader } from '../../../primitives' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives/block' import { BufferUtils } from '../../../utils' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetBlockRequest = { search?: string @@ -86,7 +86,7 @@ export const GetBlockResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.chain}/getBlock`, GetBlockRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getChainInfo.ts b/ironfish/src/rpc/routes/chain/getChainInfo.ts index f7e70d67ca..43141d3a81 100644 --- a/ironfish/src/rpc/routes/chain/getChainInfo.ts +++ b/ironfish/src/rpc/routes/chain/getChainInfo.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives/block' import { BlockHashSerdeInstance } from '../../../serde' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type BlockIdentifier = { index: string; hash: string } @@ -41,7 +41,7 @@ export const GetChainInfoResponseSchema: yup.ObjectSchema /** * Get current, heaviest and genesis block identifiers */ -router.register( +routes.register( `${ApiNamespace.chain}/getChainInfo`, GetChainInfoRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/chain/getConsensusParameters.ts b/ironfish/src/rpc/routes/chain/getConsensusParameters.ts index 9b92816e74..e6ebb9f6c4 100644 --- a/ironfish/src/rpc/routes/chain/getConsensusParameters.ts +++ b/ironfish/src/rpc/routes/chain/getConsensusParameters.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' interface ConsensusParameters { allowedBlockFuturesSeconds: number @@ -32,7 +32,7 @@ export const GetConsensusParametersResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getConsensusParameters`, GetConsensusParametersRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/chain/getDifficulty.ts b/ironfish/src/rpc/routes/chain/getDifficulty.ts index 54eab85934..b7db541d14 100644 --- a/ironfish/src/rpc/routes/chain/getDifficulty.ts +++ b/ironfish/src/rpc/routes/chain/getDifficulty.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetDifficultyRequest = | { @@ -32,7 +32,7 @@ export const GetDifficultyResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getDifficulty`, GetDifficultyRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts b/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts index b504a7e51a..25ef7762ab 100644 --- a/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts +++ b/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { BigIntUtils } from '../../../utils' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetNetworkHashPowerRequest = { blocks?: number | null // number of blocks to look back @@ -35,7 +35,7 @@ export const GetNetworkHashPowerResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getNetworkHashPower`, GetNetworkHashPowerRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getNetworkInfo.ts b/ironfish/src/rpc/routes/chain/getNetworkInfo.ts index 43a235dc3b..da11d6ca30 100644 --- a/ironfish/src/rpc/routes/chain/getNetworkInfo.ts +++ b/ironfish/src/rpc/routes/chain/getNetworkInfo.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetNetworkInfoRequest = undefined export type GetNetworkInfoResponse = { @@ -20,7 +20,7 @@ export const GetNetworkInfoResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getNetworkInfo`, GetNetworkInfoRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/chain/getNoteWitness.ts b/ironfish/src/rpc/routes/chain/getNoteWitness.ts index 31a538e3a3..16122491d0 100644 --- a/ironfish/src/rpc/routes/chain/getNoteWitness.ts +++ b/ironfish/src/rpc/routes/chain/getNoteWitness.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetNoteWitnessRequest = { index: number @@ -45,7 +45,7 @@ export const GetNoteWitnessResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getNoteWitness`, GetNoteWitnessRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getTransaction.ts b/ironfish/src/rpc/routes/chain/getTransaction.ts index 1ba924ae31..67d88e434e 100644 --- a/ironfish/src/rpc/routes/chain/getTransaction.ts +++ b/ironfish/src/rpc/routes/chain/getTransaction.ts @@ -6,7 +6,7 @@ import { Assert } from '../../../assert' import { BlockHashSerdeInstance } from '../../../serde' import { CurrencyUtils } from '../../../utils' import { NotFoundError, ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcSpend, RpcSpendSchema } from '../wallet/types' import { RpcNote, RpcNoteSchema } from './types' @@ -78,7 +78,7 @@ export const GetTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getTransaction`, GetTransactionRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/getTransactionStream.ts b/ironfish/src/rpc/routes/chain/getTransactionStream.ts index 94f82de330..a37d120399 100644 --- a/ironfish/src/rpc/routes/chain/getTransactionStream.ts +++ b/ironfish/src/rpc/routes/chain/getTransactionStream.ts @@ -10,7 +10,7 @@ import { CurrencyUtils } from '../../../utils' import { PromiseUtils } from '../../../utils/promise' import { isValidIncomingViewKey } from '../../../wallet/validator' import { ValidationError } from '../../adapters/errors' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' interface Note { assetId: string @@ -122,7 +122,7 @@ export const GetTransactionStreamResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/getTransactionStream`, GetTransactionStreamRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/chain/isValidPublicAddress.ts b/ironfish/src/rpc/routes/chain/isValidPublicAddress.ts index 5594a1cdc4..1b9796d8ed 100644 --- a/ironfish/src/rpc/routes/chain/isValidPublicAddress.ts +++ b/ironfish/src/rpc/routes/chain/isValidPublicAddress.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { isValidPublicAddress } from '../../../wallet/validator' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type IsValidPublicAddressRequest = { address: string @@ -27,7 +27,7 @@ export const IsValidPublicAddressResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.chain}/isValidPublicAddress`, IsValidPublicAddressRequestSchema, (request): void => { diff --git a/ironfish/src/rpc/routes/chain/showChain.ts b/ironfish/src/rpc/routes/chain/showChain.ts index e86b26fa3c..3d2651b19c 100644 --- a/ironfish/src/rpc/routes/chain/showChain.ts +++ b/ironfish/src/rpc/routes/chain/showChain.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { renderChain } from './utils' export type ShowChainRequest = @@ -33,7 +33,7 @@ export const ShowChainResponseSchema: yup.ObjectSchema = yup /** * Render the chain as ani ASCII graph of the block chain */ -router.register( +routes.register( `${ApiNamespace.chain}/showChain`, ShowChainRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/config/getConfig.ts b/ironfish/src/rpc/routes/config/getConfig.ts index 55d1d64792..d6f9a4acc4 100644 --- a/ironfish/src/rpc/routes/config/getConfig.ts +++ b/ironfish/src/rpc/routes/config/getConfig.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' import { ValidationError } from '../../adapters/errors' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetConfigRequest = { user?: boolean; name?: string } | undefined export type GetConfigResponse = Partial @@ -19,7 +19,7 @@ export const GetConfigRequestSchema: yup.ObjectSchema = yup export const GetConfigResponseSchema: yup.ObjectSchema = ConfigOptionsSchema -router.register( +routes.register( `${ApiNamespace.config}/getConfig`, GetConfigRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/config/setConfig.ts b/ironfish/src/rpc/routes/config/setConfig.ts index 22728a6c43..f2d73864d1 100644 --- a/ironfish/src/rpc/routes/config/setConfig.ts +++ b/ironfish/src/rpc/routes/config/setConfig.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { setUnknownConfigValue } from './uploadConfig' export type SetConfigRequest = { name: string; value: unknown } @@ -19,7 +19,7 @@ export const SetConfigRequestSchema: yup.ObjectSchema = yup export const SetConfigResponseSchema: yup.ObjectSchema = ConfigOptionsSchema -router.register( +routes.register( `${ApiNamespace.config}/setConfig`, SetConfigRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/config/unsetConfig.ts b/ironfish/src/rpc/routes/config/unsetConfig.ts index 9946f2d1ba..c592d55e92 100644 --- a/ironfish/src/rpc/routes/config/unsetConfig.ts +++ b/ironfish/src/rpc/routes/config/unsetConfig.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { setUnknownConfigValue } from './uploadConfig' export type UnsetConfigRequest = { name: string } @@ -19,7 +19,7 @@ export const UnsetConfigRequestSchema: yup.ObjectSchema = yu export const UnsetConfigResponseSchema: yup.ObjectSchema = ConfigOptionsSchema -router.register( +routes.register( `${ApiNamespace.config}/unsetConfig`, UnsetConfigRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/config/uploadConfig.ts b/ironfish/src/rpc/routes/config/uploadConfig.ts index 4dbdd1a744..3727a70f8e 100644 --- a/ironfish/src/rpc/routes/config/uploadConfig.ts +++ b/ironfish/src/rpc/routes/config/uploadConfig.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { Config, ConfigOptions, ConfigOptionsSchema } from '../../../fileStores/config' import { ValidationError } from '../../adapters/errors' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type UploadConfigRequest = { config: Record } export type UploadConfigResponse = Partial @@ -17,7 +17,7 @@ export const UploadConfigRequestSchema: yup.ObjectSchema = export const UploadConfigResponseSchema: yup.ObjectSchema = ConfigOptionsSchema -router.register( +routes.register( `${ApiNamespace.config}/uploadConfig`, UploadConfigRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/event/onGossip.ts b/ironfish/src/rpc/routes/event/onGossip.ts index 66c11209f5..0835615b73 100644 --- a/ironfish/src/rpc/routes/event/onGossip.ts +++ b/ironfish/src/rpc/routes/event/onGossip.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { BlockHeader } from '../../../primitives' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcBlockHeader, RpcBlockHeaderSchema, serializeRpcBlockHeader } from './types' export type OnGossipRequest = undefined @@ -19,7 +19,7 @@ export const OnGossipResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.event}/onGossip`, OnGossipRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/event/onReorganizeChain.ts b/ironfish/src/rpc/routes/event/onReorganizeChain.ts index efc9f09b55..047d0dde86 100644 --- a/ironfish/src/rpc/routes/event/onReorganizeChain.ts +++ b/ironfish/src/rpc/routes/event/onReorganizeChain.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { BlockHeader } from '../../../primitives' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcBlockHeader, RpcBlockHeaderSchema, serializeRpcBlockHeader } from './types' export type OnReorganizeChainRequest = undefined @@ -25,7 +25,7 @@ export const OnReorganizeChainResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.event}/onReorganizeChain`, OnReorganizeChainRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/event/onTransactionGossip.ts b/ironfish/src/rpc/routes/event/onTransactionGossip.ts index f47bab9daf..d3b355f570 100644 --- a/ironfish/src/rpc/routes/event/onTransactionGossip.ts +++ b/ironfish/src/rpc/routes/event/onTransactionGossip.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { Transaction } from '../../../primitives' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types export type OnTransactionGossipRequest = {} | undefined @@ -23,7 +23,7 @@ export const OnTransactionGossipResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.event}/onTransactionGossip`, OnTransactionGossipRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/faucet/getFunds.ts b/ironfish/src/rpc/routes/faucet/getFunds.ts index 7f0fcd5650..f634e3b5cc 100644 --- a/ironfish/src/rpc/routes/faucet/getFunds.ts +++ b/ironfish/src/rpc/routes/faucet/getFunds.ts @@ -6,7 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { WebApi } from '../../../webApi' import { ERROR_CODES, ResponseError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from '../wallet/utils' export type GetFundsRequest = { account?: string; email?: string } @@ -25,7 +25,7 @@ export const GetFundsResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.faucet}/getFunds`, GetFundsRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/mempool/getStatus.ts b/ironfish/src/rpc/routes/mempool/getStatus.ts index d009b106be..e3a4a235aa 100644 --- a/ironfish/src/rpc/routes/mempool/getStatus.ts +++ b/ironfish/src/rpc/routes/mempool/getStatus.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { PromiseUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetMempoolStatusRequest = | undefined @@ -48,7 +48,7 @@ export const GetMempoolStatusResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.mempool}/getStatus`, GetMempoolStatusRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/mempool/getTransactions.ts b/ironfish/src/rpc/routes/mempool/getTransactions.ts index 754acff7ce..b71ff28ae8 100644 --- a/ironfish/src/rpc/routes/mempool/getTransactions.ts +++ b/ironfish/src/rpc/routes/mempool/getTransactions.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { getFeeRate } from '../../../memPool' import { Transaction } from '../../../primitives' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type MinMax = { min?: number @@ -55,7 +55,7 @@ export const MempoolTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.mempool}/getTransactions`, MempoolTransactionsRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/miner/blockTemplateStream.ts b/ironfish/src/rpc/routes/miner/blockTemplateStream.ts index f28fcc6f7a..7183b996fb 100644 --- a/ironfish/src/rpc/routes/miner/blockTemplateStream.ts +++ b/ironfish/src/rpc/routes/miner/blockTemplateStream.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { SerializedBlockTemplate } from '../../../serde/BlockTemplateSerde' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type BlockTemplateStreamRequest = Record | undefined export type BlockTemplateStreamResponse = SerializedBlockTemplate @@ -41,7 +41,7 @@ export const BlockTemplateStreamResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.miner}/blockTemplateStream`, BlockTemplateStreamRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/miner/submitBlock.ts b/ironfish/src/rpc/routes/miner/submitBlock.ts index 7a928aacab..c5349fdd01 100644 --- a/ironfish/src/rpc/routes/miner/submitBlock.ts +++ b/ironfish/src/rpc/routes/miner/submitBlock.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { MINED_RESULT } from '../../../mining/manager' import { SerializedBlockTemplate } from '../../../serde' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type SubmitBlockRequest = SerializedBlockTemplate @@ -60,7 +60,7 @@ export const SubmitBlockResponseSchema: yup.ObjectSchema = }) .defined() -router.register( +routes.register( `${ApiNamespace.miner}/submitBlock`, SubmitBlockRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/node/getLogStream.ts b/ironfish/src/rpc/routes/node/getLogStream.ts index 5a8e5e89e8..d406934470 100644 --- a/ironfish/src/rpc/routes/node/getLogStream.ts +++ b/ironfish/src/rpc/routes/node/getLogStream.ts @@ -6,7 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { InterceptReporter } from '../../../logger' import { IJSON } from '../../../serde' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types export type GetLogStreamRequest = {} | undefined @@ -34,7 +34,7 @@ export const GetLogStreamResponseSchema: yup.ObjectSchema }) .defined() -router.register( +routes.register( `${ApiNamespace.node}/getLogStream`, GetLogStreamRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/node/getStatus.ts b/ironfish/src/rpc/routes/node/getStatus.ts index 142dc1bd49..46605dbe42 100644 --- a/ironfish/src/rpc/routes/node/getStatus.ts +++ b/ironfish/src/rpc/routes/node/getStatus.ts @@ -6,7 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { MathUtils, PromiseUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetNodeStatusRequest = | undefined @@ -252,7 +252,7 @@ export const GetStatusResponseSchema: yup.ObjectSchema = }) .defined() -router.register( +routes.register( `${ApiNamespace.node}/getStatus`, GetStatusRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/node/stopNode.ts b/ironfish/src/rpc/routes/node/stopNode.ts index 5ac1ce2cc5..d8a26e3e6e 100644 --- a/ironfish/src/rpc/routes/node/stopNode.ts +++ b/ironfish/src/rpc/routes/node/stopNode.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types export type StopNodeRequest = undefined @@ -17,7 +17,7 @@ export const StopNodeResponseSchema: yup.MixedSchema = yup .mixed() .oneOf([undefined] as const) -router.register( +routes.register( `${ApiNamespace.node}/stopNode`, StopNodeRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/peer/addPeer.ts b/ironfish/src/rpc/routes/peer/addPeer.ts index 1c5f72f0de..f49fb8d0dc 100644 --- a/ironfish/src/rpc/routes/peer/addPeer.ts +++ b/ironfish/src/rpc/routes/peer/addPeer.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { DEFAULT_WEBSOCKET_PORT } from '../../../fileStores/config' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type AddPeerRequest = { host: string @@ -31,7 +31,7 @@ export const AddPeerResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.peer}/addPeer`, AddPeerRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/peer/getBannedPeers.ts b/ironfish/src/rpc/routes/peer/getBannedPeers.ts index b8c83c1c12..37bb1385cd 100644 --- a/ironfish/src/rpc/routes/peer/getBannedPeers.ts +++ b/ironfish/src/rpc/routes/peer/getBannedPeers.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { PeerNetwork } from '../../../network' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type BannedPeerResponse = { identity: string @@ -43,7 +43,7 @@ export const GetBannedPeersResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.peer}/getBannedPeers`, GetBannedPeersRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/peer/getPeer.ts b/ironfish/src/rpc/routes/peer/getPeer.ts index c873d96e6e..d1e2a57100 100644 --- a/ironfish/src/rpc/routes/peer/getPeer.ts +++ b/ironfish/src/rpc/routes/peer/getPeer.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { Connection, PeerNetwork } from '../../../network' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { PeerResponse } from './getPeers' type ConnectionState = Connection['state']['type'] | '' @@ -58,7 +58,7 @@ export const GetPeerResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.peer}/getPeer`, GetPeerRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/peer/getPeerMessages.ts b/ironfish/src/rpc/routes/peer/getPeerMessages.ts index 34256ee6a8..2a90f3a804 100644 --- a/ironfish/src/rpc/routes/peer/getPeerMessages.ts +++ b/ironfish/src/rpc/routes/peer/getPeerMessages.ts @@ -6,7 +6,7 @@ import { Assert } from '../../../assert' import { Connection, PeerNetwork } from '../../../network' import { NetworkMessageType } from '../../../network/types' import { IJSON } from '../../../serde' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' type PeerMessage = { brokeringPeerDisplayName?: string @@ -58,7 +58,7 @@ export const GetPeerMessagesResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.peer}/getPeerMessages`, GetPeerMessagesRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/peer/getPeers.ts b/ironfish/src/rpc/routes/peer/getPeers.ts index c8f23d53e4..a9b419f20c 100644 --- a/ironfish/src/rpc/routes/peer/getPeers.ts +++ b/ironfish/src/rpc/routes/peer/getPeers.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { Connection, PeerNetwork } from '../../../network' import { Features } from '../../../network/peers/peerFeatures' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' type ConnectionState = Connection['state']['type'] | '' @@ -85,7 +85,7 @@ export const GetPeersResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.peer}/getPeers`, GetPeersRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/router.test.ts b/ironfish/src/rpc/routes/router.test.ts index 20a2983e70..3af6a36da5 100644 --- a/ironfish/src/rpc/routes/router.test.ts +++ b/ironfish/src/rpc/routes/router.test.ts @@ -9,7 +9,9 @@ describe('Router', () => { it('should use yup schema', async () => { const schema = yup.string().default('default') - routeTest.client.router.register('foo/bar', schema, (request) => request.end(request.data)) + routeTest.client.router.routes.register('foo/bar', schema, (request) => + request.end(request.data), + ) // should use default value from the schema let response = await routeTest.client.request('foo/bar').waitForEnd() diff --git a/ironfish/src/rpc/routes/router.ts b/ironfish/src/rpc/routes/router.ts index 6cc39fbd7d..b9ba7d2287 100644 --- a/ironfish/src/rpc/routes/router.ts +++ b/ironfish/src/rpc/routes/router.ts @@ -52,42 +52,18 @@ export function parseRoute( } export class Router { - routes = new Map>() - context: RequestContext = {} - server: RpcServer | null = null - - register( - route: string, - requestSchema: TRequestSchema, - handler: RouteHandler, TResponse>, - ): void { - const [namespace, method] = parseRoute(route) - - Assert.isNotUndefined(namespace, `Invalid namespace: ${String(namespace)}: ${route}`) - Assert.isNotUndefined(method, `Invalid method: ${String(namespace)}: ${route}`) - - let namespaceRoutes = this.routes.get(namespace) - - if (!namespaceRoutes) { - namespaceRoutes = new Map() - this.routes.set(namespace, namespaceRoutes) - } + routes = new Routes() + server: RpcServer - namespaceRoutes.set(method, { - handler: handler as RouteHandler, - schema: requestSchema, - }) + constructor(routes: Routes, server: RpcServer) { + this.routes = routes + this.server = server } async route(route: string, request: RpcRequest): Promise { const [namespace, method] = route.split('/') - const namespaceRoutes = this.routes.get(namespace) - if (!namespaceRoutes) { - throw new RouteNotFoundError(route, namespace, method) - } - - const methodRoute = namespaceRoutes.get(method) + const methodRoute = this.routes.get(namespace, method) if (!methodRoute) { throw new RouteNotFoundError(route, namespace, method) } @@ -101,7 +77,7 @@ export class Router { request.data = result try { - await handler(request, this.context) + await handler(request, this.server.context) } catch (e: unknown) { if (e instanceof ResponseError) { throw e @@ -112,11 +88,49 @@ export class Router { throw e } } +} + +class Routes { + routes = new Map>() + + get( + namespace: string, + method: string, + ): { handler: RouteHandler; schema: YupSchema } | undefined { + const namespaceRoutes = this.routes.get(namespace) + if (!namespaceRoutes) { + return undefined + } + + return namespaceRoutes.get(method) + } + + register( + route: string, + requestSchema: TRequestSchema, + handler: RouteHandler, TResponse>, + ): void { + const [namespace, method] = parseRoute(route) + + Assert.isNotUndefined(namespace, `Invalid namespace: ${String(namespace)}: ${route}`) + Assert.isNotUndefined(method, `Invalid method: ${String(namespace)}: ${route}`) + + let namespaceRoutes = this.routes.get(namespace) + + if (!namespaceRoutes) { + namespaceRoutes = new Map() + this.routes.set(namespace, namespaceRoutes) + } + + namespaceRoutes.set(method, { + handler: handler as RouteHandler, + schema: requestSchema, + }) + } - filter(namespaces: string[]): Router { + filter(namespaces: string[]): Routes { const set = new Set(namespaces) - const copy = new Router() - copy.server = this.server + const copy = new Routes() for (const [key, value] of this.routes) { if (set.has(key)) { @@ -128,4 +142,4 @@ export class Router { } } -export const router = new Router() +export const routes = new Routes() diff --git a/ironfish/src/rpc/routes/rpc/getStatus.ts b/ironfish/src/rpc/routes/rpc/getStatus.ts index 5930f29fe7..0adcec1e8b 100644 --- a/ironfish/src/rpc/routes/rpc/getStatus.ts +++ b/ironfish/src/rpc/routes/rpc/getStatus.ts @@ -7,7 +7,7 @@ import { IronfishNode } from '../../../node' import { PromiseUtils } from '../../../utils' import { RpcHttpAdapter, RpcIpcAdapter } from '../../adapters' import { RpcSocketAdapter } from '../../adapters/socketAdapter/socketAdapter' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetRpcStatusRequest = | undefined @@ -60,7 +60,7 @@ export const GetRpcStatusResponseSchema: yup.ObjectSchema }) .defined() -router.register( +routes.register( `${ApiNamespace.rpc}/getStatus`, GetRpcStatusRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/addTransaction.ts b/ironfish/src/rpc/routes/wallet/addTransaction.ts index 9d12231e64..bff38eda1f 100644 --- a/ironfish/src/rpc/routes/wallet/addTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/addTransaction.ts @@ -6,7 +6,7 @@ import { Assert } from '../../../assert' import { Transaction } from '../../../primitives' import { AsyncUtils } from '../../../utils' import { ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type AddTransactionRequest = { transaction: string @@ -34,7 +34,7 @@ export const AddTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/addTransaction`, AddTransactionRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.ts b/ironfish/src/rpc/routes/wallet/burnAsset.ts index 3da10513d5..1d9ca7313e 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils, YupUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export interface BurnAssetRequest { @@ -45,7 +45,7 @@ export const BurnAssetResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/burnAsset`, BurnAssetRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/create.ts b/ironfish/src/rpc/routes/wallet/create.ts index a0a933ff07..b95c094347 100644 --- a/ironfish/src/rpc/routes/wallet/create.ts +++ b/ironfish/src/rpc/routes/wallet/create.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { ERROR_CODES, ValidationError } from '../../adapters' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type CreateAccountRequest = { name: string; default?: boolean } export type CreateAccountResponse = { @@ -28,7 +28,7 @@ export const CreateAccountResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/create`, CreateAccountRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index a638a50954..8081d5c494 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -14,7 +14,7 @@ import { CurrencyUtils, YupUtils } from '../../../utils' import { Wallet } from '../../../wallet' import { NotEnoughFundsError } from '../../../wallet/errors' import { ERROR_CODES, ValidationError } from '../../adapters/errors' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type CreateTransactionRequest = { @@ -99,7 +99,7 @@ export const CreateTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/createTransaction`, CreateTransactionRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/exportAccount.ts b/ironfish/src/rpc/routes/wallet/exportAccount.ts index d1d96b0e94..8b18b95369 100644 --- a/ironfish/src/rpc/routes/wallet/exportAccount.ts +++ b/ironfish/src/rpc/routes/wallet/exportAccount.ts @@ -6,7 +6,7 @@ import { Assert } from '../../../assert' import { LanguageKey, LanguageUtils } from '../../../utils' import { encodeAccount } from '../../../wallet/account/encoder/account' import { AccountFormat } from '../../../wallet/account/encoder/encoder' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcAccountImport } from './types' import { getAccount } from './utils' @@ -35,7 +35,7 @@ export const ExportAccountResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/exportAccount`, ExportAccountRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts index 1c0d5ad5ab..4c5931a40e 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountNotesStream.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcWalletNote, RpcWalletNoteSchema } from './types' import { getAccount } from './utils' @@ -22,7 +22,7 @@ export const GetAccountNotesStreamRequestSchema: yup.ObjectSchema = RpcWalletNoteSchema -router.register( +routes.register( `${ApiNamespace.wallet}/getAccountNotesStream`, GetAccountNotesStreamRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts index a52552a3cd..57e51e2b5c 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransaction.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { TransactionStatus, TransactionType } from '../../../wallet' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcSpend, RpcSpendSchema, RpcWalletNote, RpcWalletNoteSchema } from './types' import { getAccount, @@ -87,7 +87,7 @@ export const GetAccountTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/getAccountTransaction`, GetAccountTransactionRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts b/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts index 6e4f8ce632..853d8a31fa 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountTransactions.ts @@ -9,7 +9,7 @@ import { TransactionStatus, TransactionType } from '../../../wallet' import { Account } from '../../../wallet/account/account' import { TransactionValue } from '../../../wallet/walletdb/transactionValue' import { RpcRequest } from '../../request' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcSpend, RpcSpendSchema, RpcWalletNote, RpcWalletNoteSchema } from './types' import { getAccount, @@ -96,7 +96,7 @@ export const GetAccountTransactionsResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/getAccountTransactions`, GetAccountTransactionsRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getAccounts.ts b/ironfish/src/rpc/routes/wallet/getAccounts.ts index ffa3277baf..90eeb3d614 100644 --- a/ironfish/src/rpc/routes/wallet/getAccounts.ts +++ b/ironfish/src/rpc/routes/wallet/getAccounts.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { Account } from '../../../wallet' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types export type GetAccountsRequest = { default?: boolean; displayName?: boolean } | undefined @@ -23,7 +23,7 @@ export const GetAccountsResponseSchema: yup.ObjectSchema = }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/getAccounts`, GetAccountsRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts b/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts index 7e7a7cac16..d15d6aa65b 100644 --- a/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts +++ b/ironfish/src/rpc/routes/wallet/getAccountsStatus.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetAccountStatusRequest = { account?: string } @@ -39,7 +39,7 @@ export const GetAccountStatusResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/getAccountsStatus`, GetAccountStatusRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getAssets.ts b/ironfish/src/rpc/routes/wallet/getAssets.ts index 506618c1ec..8649d2b68f 100644 --- a/ironfish/src/rpc/routes/wallet/getAssets.ts +++ b/ironfish/src/rpc/routes/wallet/getAssets.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { AssetVerification } from '../../../assets' import { CurrencyUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type GetAssetsRequest = { @@ -47,7 +47,7 @@ export const GetAssetsResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/getAssets`, GetAssetsRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getBalance.ts b/ironfish/src/rpc/routes/wallet/getBalance.ts index 8bfdb00d57..ddb7f331db 100644 --- a/ironfish/src/rpc/routes/wallet/getBalance.ts +++ b/ironfish/src/rpc/routes/wallet/getBalance.ts @@ -5,7 +5,7 @@ import { Asset } from '@ironfish/rust-nodejs' import * as yup from 'yup' import { Assert } from '../../../assert' import { AssetVerification } from '../../../assets' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type GetBalanceRequest = @@ -58,7 +58,7 @@ export const GetBalanceResponseSchema: yup.ObjectSchema = yu }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/getBalance`, GetBalanceRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getBalances.ts b/ironfish/src/rpc/routes/wallet/getBalances.ts index 22083a9cf7..ebc699dd2a 100644 --- a/ironfish/src/rpc/routes/wallet/getBalances.ts +++ b/ironfish/src/rpc/routes/wallet/getBalances.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { AssetVerification } from '../../../assets' import { CurrencyUtils } from '../../../utils' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export interface GetBalancesRequest { @@ -70,7 +70,7 @@ export const GetBalancesResponseSchema: yup.ObjectSchema = }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/getBalances`, GetBalancesRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts b/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts index f7d0b9c715..bee9f0c986 100644 --- a/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts +++ b/ironfish/src/rpc/routes/wallet/getDefaultAccount.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' // eslint-disable-next-line @typescript-eslint/ban-types export type GetDefaultAccountRequest = {} | undefined @@ -25,7 +25,7 @@ export const GetDefaultAccountResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/getDefaultAccount`, GetDefaultAccountRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/wallet/getNotes.ts b/ironfish/src/rpc/routes/wallet/getNotes.ts index 4cc30e301d..ac05c656ba 100644 --- a/ironfish/src/rpc/routes/wallet/getNotes.ts +++ b/ironfish/src/rpc/routes/wallet/getNotes.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcWalletNote, RpcWalletNoteSchema } from './types' import { getAccount, serializeRpcWalletNote } from './utils' @@ -69,7 +69,7 @@ export const GetNotesResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/getNotes`, GetNotesRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/getPublicKey.ts b/ironfish/src/rpc/routes/wallet/getPublicKey.ts index 6fa9d67d17..d8c545fe42 100644 --- a/ironfish/src/rpc/routes/wallet/getPublicKey.ts +++ b/ironfish/src/rpc/routes/wallet/getPublicKey.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type GetPublicKeyRequest = { account?: string } @@ -22,7 +22,7 @@ export const GetPublicKeyResponseSchema: yup.ObjectSchema }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/getPublicKey`, GetPublicKeyRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/routes/wallet/importAccount.ts b/ironfish/src/rpc/routes/wallet/importAccount.ts index a6c65e33bf..ec20947b43 100644 --- a/ironfish/src/rpc/routes/wallet/importAccount.ts +++ b/ironfish/src/rpc/routes/wallet/importAccount.ts @@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid' import * as yup from 'yup' import { Assert } from '../../../assert' import { decodeAccount } from '../../../wallet/account/encoder/account' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { RpcAccountImport } from './types' import { deserializeRpcAccountImport } from './utils' @@ -37,7 +37,7 @@ export const ImportAccountResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/importAccount`, ImportAccountRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.ts b/ironfish/src/rpc/routes/wallet/mintAsset.ts index 4e5f327869..c3b176d50b 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.ts @@ -6,7 +6,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { CurrencyUtils, YupUtils } from '../../../utils' import { MintAssetOptions } from '../../../wallet/interfaces/mintAssetOptions' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export interface MintAssetRequest { @@ -51,7 +51,7 @@ export const MintAssetResponseSchema: yup.ObjectSchema = yup }) .defined() -router.register( +routes.register( `${ApiNamespace.wallet}/mintAsset`, MintAssetRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/postTransaction.ts b/ironfish/src/rpc/routes/wallet/postTransaction.ts index ddec0d2d6a..1115b7772c 100644 --- a/ironfish/src/rpc/routes/wallet/postTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/postTransaction.ts @@ -4,7 +4,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { RawTransactionSerde } from '../../../primitives/rawTransaction' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type PostTransactionRequest = { @@ -33,7 +33,7 @@ export const PostTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/postTransaction`, PostTransactionRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/remove.ts b/ironfish/src/rpc/routes/wallet/remove.ts index 8c8844d576..319253e203 100644 --- a/ironfish/src/rpc/routes/wallet/remove.ts +++ b/ironfish/src/rpc/routes/wallet/remove.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type RemoveAccountRequest = { account: string; confirm?: boolean; wait?: boolean } @@ -23,7 +23,7 @@ export const RemoveAccountResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/remove`, RemoveAccountRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/rename.ts b/ironfish/src/rpc/routes/wallet/rename.ts index 08accd9562..b4ba365166 100644 --- a/ironfish/src/rpc/routes/wallet/rename.ts +++ b/ironfish/src/rpc/routes/wallet/rename.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type RenameAccountRequest = { account: string; newName: string } @@ -20,7 +20,7 @@ export const RenameAccountResponseSchema: yup.MixedSchema .mixed() .oneOf([undefined] as const) -router.register( +routes.register( `${ApiNamespace.wallet}/rename`, RenameAccountRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/rescanAccount.ts b/ironfish/src/rpc/routes/wallet/rescanAccount.ts index a259530a20..b16520ec91 100644 --- a/ironfish/src/rpc/routes/wallet/rescanAccount.ts +++ b/ironfish/src/rpc/routes/wallet/rescanAccount.ts @@ -5,7 +5,7 @@ import * as yup from 'yup' import { Assert } from '../../../assert' import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives' import { ValidationError } from '../../adapters/errors' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type RescanAccountRequest = { follow?: boolean; from?: number } export type RescanAccountResponse = { sequence: number; startedAt: number; endSequence: number } @@ -25,7 +25,7 @@ export const RescanAccountResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/rescanAccount`, RescanAccountRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.ts index 3b4522e2d0..d3c14497f7 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.ts @@ -9,7 +9,7 @@ import { CurrencyUtils, YupUtils } from '../../../utils' import { Wallet } from '../../../wallet' import { NotEnoughFundsError } from '../../../wallet/errors' import { ERROR_CODES, ValidationError } from '../../adapters/errors' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type SendTransactionRequest = { @@ -64,7 +64,7 @@ export const SendTransactionResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.wallet}/sendTransaction`, SendTransactionRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/wallet/use.ts b/ironfish/src/rpc/routes/wallet/use.ts index 394d3f0843..e8b708794f 100644 --- a/ironfish/src/rpc/routes/wallet/use.ts +++ b/ironfish/src/rpc/routes/wallet/use.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { Assert } from '../../../assert' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' import { getAccount } from './utils' export type UseAccountRequest = { account: string } @@ -19,7 +19,7 @@ export const UseAccountResponseSchema: yup.MixedSchema = yup .mixed() .oneOf([undefined] as const) -router.register( +routes.register( `${ApiNamespace.wallet}/use`, UseAccountRequestSchema, async (request, { node }): Promise => { diff --git a/ironfish/src/rpc/routes/worker/getStatus.ts b/ironfish/src/rpc/routes/worker/getStatus.ts index c1676bf7a7..2c990d1eaa 100644 --- a/ironfish/src/rpc/routes/worker/getStatus.ts +++ b/ironfish/src/rpc/routes/worker/getStatus.ts @@ -6,7 +6,7 @@ import { Assert } from '../../../assert' import { IronfishNode } from '../../../node' import { MathUtils } from '../../../utils' import { WorkerMessageType } from '../../../workerPool/tasks/workerMessage' -import { ApiNamespace, router } from '../router' +import { ApiNamespace, routes } from '../router' export type GetWorkersStatusRequest = | undefined @@ -63,7 +63,7 @@ export const GetWorkersStatusResponseSchema: yup.ObjectSchema( +routes.register( `${ApiNamespace.worker}/getStatus`, GetWorkersStatusRequestSchema, (request, { node }): void => { diff --git a/ironfish/src/rpc/server.ts b/ironfish/src/rpc/server.ts index 9965e6a73d..1bd4dc9218 100644 --- a/ironfish/src/rpc/server.ts +++ b/ironfish/src/rpc/server.ts @@ -5,7 +5,7 @@ import { InternalStore } from '../fileStores' import { createRootLogger, Logger } from '../logger' import { IRpcAdapter } from './adapters' -import { ApiNamespace, RequestContext, Router, router } from './routes' +import { ApiNamespace, RequestContext, Router, routes } from './routes' export class RpcServer { readonly internal: InternalStore @@ -32,10 +32,7 @@ export class RpcServer { /** Creates a new router from this RpcServer with the attached routes filtered by namespaces */ getRouter(namespaces: ApiNamespace[]): Router { - const newRouter = router.filter(namespaces) - newRouter.server = this - newRouter.context = this.context - return newRouter + return new Router(routes.filter(namespaces), this) } /** Starts the RPC server and tells any attached adapters to starts serving requests to the routing layer */ From 48495c06e459c45d539405b8705d86aa45655a55 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:33:16 -0400 Subject: [PATCH 34/37] refactor(ironfish): Encapsulate meta store in `BlockchainDB` (#4044) * refactor(ironfish): Encapsulate block header DB operations * refactor(ironfish): Encapsulate meta store in `BlockchainDB` --- ironfish/src/blockchain/blockchain.ts | 23 ++++--------- .../src/blockchain/database/blockchaindb.ts | 34 +++++++++++++++++-- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 1bd0cd033d..d350307524 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -47,7 +47,6 @@ import { IDatabase, IDatabaseStore, IDatabaseTransaction, - StringEncoding, U32_ENCODING, } from '../storage' import { Strategy } from '../strategy' @@ -61,7 +60,6 @@ import { NullifierSet } from './nullifierSet/nullifierSet' import { AssetSchema, HashToNextSchema, - MetaSchema, SequenceToHashesSchema, SequenceToHashSchema, TransactionHashToBlockHashSchema, @@ -100,8 +98,6 @@ export class Blockchain { // Whether to seed the chain with a genesis block when opening the database. autoSeed: boolean - // Contains flat fields - meta: IDatabaseStore // BlockHash -> BlockHeader transactions: IDatabaseStore // Sequence -> BlockHash[] @@ -201,13 +197,6 @@ export class Blockchain { // will be removed once all stores are migrated this.db = this.blockchainDb.db - // Flat Fields - this.meta = this.db.addStore({ - name: 'bm', - keyEncoding: new StringEncoding<'head' | 'latest'>(), - valueEncoding: BUFFER_ENCODING, - }) - // BlockHash -> Transaction[] this.transactions = this.db.addStore({ name: 'bt', @@ -327,7 +316,7 @@ export class Blockchain { this.latest = this.genesis } - const headHash = await this.meta.get('head') + const headHash = await this.blockchainDb.getMetaHash('head') if (headHash) { const head = await this.getHeader(headHash) Assert.isNotNull( @@ -339,7 +328,7 @@ export class Blockchain { this.head = head } - const latestHash = await this.meta.get('latest') + const latestHash = await this.blockchainDb.getMetaHash('latest') if (latestHash) { const latest = await this.getHeader(latestHash) Assert.isNotNull( @@ -1167,7 +1156,7 @@ export class Blockchain { // TODO: use a new heads table to recalculate this if (this.latest.hash.equals(hash)) { this.latest = this.head - await this.meta.put('latest', this.head.hash, tx) + await this.blockchainDb.putMetaHash('latest', this.head.hash, tx) } }) } @@ -1262,7 +1251,7 @@ export class Blockchain { } await this.sequenceToHash.put(block.header.sequence, block.header.hash, tx) - await this.meta.put('head', block.header.hash, tx) + await this.blockchainDb.putMetaHash('head', block.header.hash, tx) // If the tree sizes don't match the previous block, we can't verify if the tree // sizes on this block are correct @@ -1320,7 +1309,7 @@ export class Blockchain { this.nullifiers.disconnectBlock(block, tx), ]) - await this.meta.put('head', prev.hash, tx) + await this.blockchainDb.putMetaHash('head', prev.hash, tx) await tx.update() } @@ -1349,7 +1338,7 @@ export class Blockchain { } if (!this.hasGenesisBlock || isBlockLater(block.header, this.latest)) { - await this.meta.put('latest', hash, tx) + await this.blockchainDb.putMetaHash('latest', hash, tx) } } diff --git a/ironfish/src/blockchain/database/blockchaindb.ts b/ironfish/src/blockchain/database/blockchaindb.ts index 245d6a1b3e..caf3fe54a5 100644 --- a/ironfish/src/blockchain/database/blockchaindb.ts +++ b/ironfish/src/blockchain/database/blockchaindb.ts @@ -3,9 +3,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { FileSystem } from '../../fileSystems' import { BlockHeader } from '../../primitives' -import { BUFFER_ENCODING, IDatabase, IDatabaseStore, IDatabaseTransaction } from '../../storage' +import { + BUFFER_ENCODING, + IDatabase, + IDatabaseStore, + IDatabaseTransaction, + StringEncoding, +} from '../../storage' import { createDB } from '../../storage/utils' -import { HeadersSchema } from '../schema' +import { HeadersSchema, MetaSchema } from '../schema' import { HeaderEncoding, HeaderValue } from './headers' export const VERSION_DATABASE_CHAIN = 14 @@ -17,6 +23,8 @@ export class BlockchainDB { // BlockHash -> BlockHeader headers: IDatabaseStore + // Contains flat fields + meta: IDatabaseStore constructor(options: { location: string; files: FileSystem }) { this.location = options.location @@ -29,6 +37,13 @@ export class BlockchainDB { keyEncoding: BUFFER_ENCODING, valueEncoding: new HeaderEncoding(), }) + + // Flat Fields + this.meta = this.db.addStore({ + name: 'bm', + keyEncoding: new StringEncoding<'head' | 'latest'>(), + valueEncoding: BUFFER_ENCODING, + }) } async open(): Promise { @@ -59,4 +74,19 @@ export class BlockchainDB { ): Promise { return this.headers.put(hash, header, tx) } + + async getMetaHash( + key: 'head' | 'latest', + tx?: IDatabaseTransaction, + ): Promise { + return this.meta.get(key, tx) + } + + async putMetaHash( + key: 'head' | 'latest', + value: Buffer, + tx?: IDatabaseTransaction, + ): Promise { + return this.meta.put(key, value, tx) + } } From 5aeceb840e64b0d3a71700e6c721f85f8791bc79 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:48:27 -0400 Subject: [PATCH 35/37] refactor(ironfish): Encapsulate transactions store in `BlockchainDB` (#4046) * refactor(ironfish): Encapsulate block header DB operations * refactor(ironfish): Encapsulate meta store in `BlockchainDB` * refactor(ironfish): Encapsulate transactions store in `BlockchainDB` * fix(ironfish): Remove `private` for blockchainDB --- ironfish/src/blockchain/blockchain.ts | 28 +++++++------ .../src/blockchain/database/blockchaindb.ts | 39 ++++++++++++++++++- ironfish/src/network/peerNetwork.test.ts | 4 +- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index d350307524..8a34f202dd 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -55,7 +55,7 @@ import { WorkerPool } from '../workerPool' import { AssetValue, AssetValueEncoding } from './database/assetValue' import { BlockchainDB } from './database/blockchaindb' import { SequenceToHashesValueEncoding } from './database/sequenceToHashes' -import { TransactionsValueEncoding } from './database/transactions' +import { TransactionsValue } from './database/transactions' import { NullifierSet } from './nullifierSet/nullifierSet' import { AssetSchema, @@ -63,7 +63,6 @@ import { SequenceToHashesSchema, SequenceToHashSchema, TransactionHashToBlockHashSchema, - TransactionsSchema, } from './schema' export const VERSION_DATABASE_CHAIN = 14 @@ -79,7 +78,7 @@ export class Blockchain { consensus: Consensus seedGenesisBlock: SerializedBlock config: Config - blockchainDb: BlockchainDB + readonly blockchainDb: BlockchainDB synced = false opened = false @@ -98,8 +97,6 @@ export class Blockchain { // Whether to seed the chain with a genesis block when opening the database. autoSeed: boolean - // BlockHash -> BlockHeader - transactions: IDatabaseStore // Sequence -> BlockHash[] sequenceToHashes: IDatabaseStore // Sequence -> BlockHash @@ -197,13 +194,6 @@ export class Blockchain { // will be removed once all stores are migrated this.db = this.blockchainDb.db - // BlockHash -> Transaction[] - this.transactions = this.db.addStore({ - name: 'bt', - keyEncoding: BUFFER_ENCODING, - valueEncoding: new TransactionsValueEncoding(), - }) - // number -> BlockHash[] this.sequenceToHashes = this.db.addStore({ name: 'bs', @@ -861,7 +851,7 @@ export class Blockchain { return this.db.withTransaction(tx, async (tx) => { const [header, transactions] = await Promise.all([ blockHeader || this.blockchainDb.getBlockHeader(blockHash, tx), - this.transactions.get(blockHash, tx), + this.blockchainDb.getTransactions(blockHash, tx), ]) if (!header && !transactions) { @@ -912,6 +902,14 @@ export class Blockchain { return hashes.hashes } + async putTransaction( + hash: Buffer, + value: TransactionsValue, + tx?: IDatabaseTransaction, + ): Promise { + return this.blockchainDb.putTransaction(hash, value, tx) + } + /** * Create a new block on the chain. * @@ -1150,7 +1148,7 @@ export class Blockchain { await this.sequenceToHashes.put(header.sequence, { hashes }, tx) } - await this.transactions.del(hash, tx) + await this.blockchainDb.deleteTransaction(hash, tx) await this.blockchainDb.deleteHeader(hash, tx) // TODO: use a new heads table to recalculate this @@ -1327,7 +1325,7 @@ export class Blockchain { await this.blockchainDb.putBlockHeader(hash, { header: block.header }, tx) // Update BlockHash -> Transaction - await this.transactions.add(hash, { transactions: block.transactions }, tx) + await this.blockchainDb.addTransaction(hash, { transactions: block.transactions }, tx) // Update Sequence -> BlockHash[] const hashes = await this.sequenceToHashes.get(sequence, tx) diff --git a/ironfish/src/blockchain/database/blockchaindb.ts b/ironfish/src/blockchain/database/blockchaindb.ts index caf3fe54a5..ddcf92c05f 100644 --- a/ironfish/src/blockchain/database/blockchaindb.ts +++ b/ironfish/src/blockchain/database/blockchaindb.ts @@ -11,8 +11,9 @@ import { StringEncoding, } from '../../storage' import { createDB } from '../../storage/utils' -import { HeadersSchema, MetaSchema } from '../schema' +import { HeadersSchema, MetaSchema, TransactionsSchema } from '../schema' import { HeaderEncoding, HeaderValue } from './headers' +import { TransactionsValue, TransactionsValueEncoding } from './transactions' export const VERSION_DATABASE_CHAIN = 14 @@ -25,6 +26,8 @@ export class BlockchainDB { headers: IDatabaseStore // Contains flat fields meta: IDatabaseStore + // BlockHash -> BlockHeader + transactions: IDatabaseStore constructor(options: { location: string; files: FileSystem }) { this.location = options.location @@ -44,6 +47,13 @@ export class BlockchainDB { keyEncoding: new StringEncoding<'head' | 'latest'>(), valueEncoding: BUFFER_ENCODING, }) + + // BlockHash -> Transaction[] + this.transactions = this.db.addStore({ + name: 'bt', + keyEncoding: BUFFER_ENCODING, + valueEncoding: new TransactionsValueEncoding(), + }) } async open(): Promise { @@ -89,4 +99,31 @@ export class BlockchainDB { ): Promise { return this.meta.put(key, value, tx) } + + async getTransactions( + blockHash: Buffer, + tx?: IDatabaseTransaction, + ): Promise { + return this.transactions.get(blockHash, tx) + } + + async addTransaction( + hash: Buffer, + value: TransactionsValue, + tx?: IDatabaseTransaction, + ): Promise { + return this.transactions.add(hash, value, tx) + } + + async putTransaction( + hash: Buffer, + value: TransactionsValue, + tx?: IDatabaseTransaction, + ): Promise { + return this.transactions.put(hash, value, tx) + } + + async deleteTransaction(hash: Buffer, tx?: IDatabaseTransaction): Promise { + return this.transactions.del(hash, tx) + } } diff --git a/ironfish/src/network/peerNetwork.test.ts b/ironfish/src/network/peerNetwork.test.ts index 5959b73af1..04941310e2 100644 --- a/ironfish/src/network/peerNetwork.test.ts +++ b/ironfish/src/network/peerNetwork.test.ts @@ -198,7 +198,7 @@ describe('PeerNetwork', () => { await expect(node.chain).toAddBlock(block) - await node.chain.transactions.put(block.header.hash, { + await node.chain.putTransaction(block.header.hash, { transactions: [transaction1, transaction2, transaction3], }) @@ -278,7 +278,7 @@ describe('PeerNetwork', () => { await expect(node.chain).toAddBlock(block) - await node.chain.transactions.put(block.header.hash, { + await node.chain.putTransaction(block.header.hash, { transactions: [transaction1, transaction2, transaction3], }) From 8f7f1864657c7089062bf3ea50b6570d7f853ecc Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:16:26 -0400 Subject: [PATCH 36/37] refactor(ironfish): Encapsulate sequence to hashes db operations (#4047) * refactor(ironfish): Encapsulate block header DB operations * refactor(ironfish): Encapsulate meta store in `BlockchainDB` * refactor(ironfish): Encapsulate transactions store in `BlockchainDB` * refactor(ironfish): Encapsulate sequence to hash db operations * fix(ironfish): Remove `private` for blockchainDB --- ironfish/src/blockchain/blockchain.ts | 47 +++----------- .../src/blockchain/database/blockchaindb.ts | 65 ++++++++++++++++++- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/ironfish/src/blockchain/blockchain.ts b/ironfish/src/blockchain/blockchain.ts index 8a34f202dd..c910a3d441 100644 --- a/ironfish/src/blockchain/blockchain.ts +++ b/ironfish/src/blockchain/blockchain.ts @@ -54,13 +54,11 @@ import { AsyncUtils, BenchUtils, HashUtils } from '../utils' import { WorkerPool } from '../workerPool' import { AssetValue, AssetValueEncoding } from './database/assetValue' import { BlockchainDB } from './database/blockchaindb' -import { SequenceToHashesValueEncoding } from './database/sequenceToHashes' import { TransactionsValue } from './database/transactions' import { NullifierSet } from './nullifierSet/nullifierSet' import { AssetSchema, HashToNextSchema, - SequenceToHashesSchema, SequenceToHashSchema, TransactionHashToBlockHashSchema, } from './schema' @@ -97,8 +95,6 @@ export class Blockchain { // Whether to seed the chain with a genesis block when opening the database. autoSeed: boolean - // Sequence -> BlockHash[] - sequenceToHashes: IDatabaseStore // Sequence -> BlockHash sequenceToHash: IDatabaseStore // BlockHash -> BlockHash @@ -194,13 +190,6 @@ export class Blockchain { // will be removed once all stores are migrated this.db = this.blockchainDb.db - // number -> BlockHash[] - this.sequenceToHashes = this.db.addStore({ - name: 'bs', - keyEncoding: U32_ENCODING, - valueEncoding: new SequenceToHashesValueEncoding(), - }) - // number -> BlockHash this.sequenceToHash = this.db.addStore({ name: 'bS', @@ -893,13 +882,7 @@ export class Blockchain { * Returns an array of hashes for any blocks at the given sequence */ async getHashesAtSequence(sequence: number, tx?: IDatabaseTransaction): Promise { - const hashes = await this.sequenceToHashes.get(sequence, tx) - - if (!hashes) { - return [] - } - - return hashes.hashes + return this.blockchainDb.getBlockHashesAtSequence(sequence, tx) } async putTransaction( @@ -1071,21 +1054,7 @@ export class Blockchain { sequence: number, tx?: IDatabaseTransaction, ): Promise { - const hashes = await this.sequenceToHashes.get(sequence, tx) - - if (!hashes) { - return [] - } - - const headers = await Promise.all( - hashes.hashes.map(async (h) => { - const header = await this.getHeader(h, tx) - Assert.isNotNull(header) - return header - }), - ) - - return headers + return this.blockchainDb.getBlockHeadersAtSequence(sequence, tx) } async isHeadChain(header: BlockHeader, tx?: IDatabaseTransaction): Promise { @@ -1140,12 +1109,12 @@ export class Blockchain { await this.disconnect(block, tx) } - const result = await this.sequenceToHashes.get(header.sequence, tx) - const hashes = (result?.hashes || []).filter((h) => !h.equals(hash)) + const result = await this.blockchainDb.getBlockHashesAtSequence(header.sequence, tx) + const hashes = result.filter((h) => !h.equals(hash)) if (hashes.length === 0) { - await this.sequenceToHashes.del(header.sequence, tx) + await this.blockchainDb.deleteSequenceToHashes(header.sequence, tx) } else { - await this.sequenceToHashes.put(header.sequence, { hashes }, tx) + await this.blockchainDb.putSequenceToHashes(header.sequence, hashes, tx) } await this.blockchainDb.deleteTransaction(hash, tx) @@ -1328,8 +1297,8 @@ export class Blockchain { await this.blockchainDb.addTransaction(hash, { transactions: block.transactions }, tx) // Update Sequence -> BlockHash[] - const hashes = await this.sequenceToHashes.get(sequence, tx) - await this.sequenceToHashes.put(sequence, { hashes: [...(hashes?.hashes || []), hash] }, tx) + const hashes = await this.blockchainDb.getBlockHashesAtSequence(sequence, tx) + await this.blockchainDb.putSequenceToHashes(sequence, [...hashes, hash], tx) if (!fork) { await this.saveConnect(block, prev, tx) diff --git a/ironfish/src/blockchain/database/blockchaindb.ts b/ironfish/src/blockchain/database/blockchaindb.ts index ddcf92c05f..1854996a25 100644 --- a/ironfish/src/blockchain/database/blockchaindb.ts +++ b/ironfish/src/blockchain/database/blockchaindb.ts @@ -1,18 +1,27 @@ /* 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 } from '../../assert' import { FileSystem } from '../../fileSystems' import { BlockHeader } from '../../primitives' +import { BlockHash } from '../../primitives/blockheader' import { BUFFER_ENCODING, IDatabase, IDatabaseStore, IDatabaseTransaction, StringEncoding, + U32_ENCODING, } from '../../storage' import { createDB } from '../../storage/utils' -import { HeadersSchema, MetaSchema, TransactionsSchema } from '../schema' +import { + HeadersSchema, + MetaSchema, + SequenceToHashesSchema, + TransactionsSchema, +} from '../schema' import { HeaderEncoding, HeaderValue } from './headers' +import { SequenceToHashesValueEncoding } from './sequenceToHashes' import { TransactionsValue, TransactionsValueEncoding } from './transactions' export const VERSION_DATABASE_CHAIN = 14 @@ -28,6 +37,8 @@ export class BlockchainDB { meta: IDatabaseStore // BlockHash -> BlockHeader transactions: IDatabaseStore + // Sequence -> BlockHash[] + sequenceToHashes: IDatabaseStore constructor(options: { location: string; files: FileSystem }) { this.location = options.location @@ -54,6 +65,13 @@ export class BlockchainDB { keyEncoding: BUFFER_ENCODING, valueEncoding: new TransactionsValueEncoding(), }) + + // number -> BlockHash[] + this.sequenceToHashes = this.db.addStore({ + name: 'bs', + keyEncoding: U32_ENCODING, + valueEncoding: new SequenceToHashesValueEncoding(), + }) } async open(): Promise { @@ -126,4 +144,49 @@ export class BlockchainDB { async deleteTransaction(hash: Buffer, tx?: IDatabaseTransaction): Promise { return this.transactions.del(hash, tx) } + + async getBlockHashesAtSequence( + sequence: number, + tx?: IDatabaseTransaction, + ): Promise { + const hashes = await this.sequenceToHashes.get(sequence, tx) + if (!hashes) { + return [] + } + + return hashes.hashes + } + + async getBlockHeadersAtSequence( + sequence: number, + tx?: IDatabaseTransaction, + ): Promise { + const hashes = await this.sequenceToHashes.get(sequence, tx) + + if (!hashes) { + return [] + } + + const headers = await Promise.all( + hashes.hashes.map(async (h) => { + const header = await this.getBlockHeader(h, tx) + Assert.isNotUndefined(header) + return header + }), + ) + + return headers + } + + async deleteSequenceToHashes(sequence: number, tx?: IDatabaseTransaction): Promise { + return this.sequenceToHashes.del(sequence, tx) + } + + async putSequenceToHashes( + sequence: number, + hashes: Buffer[], + tx?: IDatabaseTransaction, + ): Promise { + return this.sequenceToHashes.put(sequence, { hashes }, tx) + } } From 7676c8b26bb583bd025f6ec4cd3f0b740f025719 Mon Sep 17 00:00:00 2001 From: Andrea Corbellini Date: Mon, 17 Jul 2023 11:03:27 -0700 Subject: [PATCH 37/37] Bump node version to 1.6.0 --- ironfish-cli/package.json | 6 +++--- ironfish-rust-nodejs/npm/darwin-arm64/package.json | 2 +- ironfish-rust-nodejs/npm/darwin-x64/package.json | 2 +- ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json | 2 +- ironfish-rust-nodejs/npm/linux-arm64-musl/package.json | 2 +- ironfish-rust-nodejs/npm/linux-x64-gnu/package.json | 2 +- ironfish-rust-nodejs/npm/linux-x64-musl/package.json | 2 +- ironfish-rust-nodejs/npm/win32-x64-msvc/package.json | 2 +- ironfish-rust-nodejs/package.json | 2 +- ironfish/package.json | 4 ++-- simulator/package.json | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 47edc7aa6a..40befcbff6 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -1,6 +1,6 @@ { "name": "ironfish", - "version": "1.5.0", + "version": "1.6.0", "description": "CLI for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -59,8 +59,8 @@ "@aws-sdk/client-s3": "3", "@aws-sdk/client-secrets-manager": "3", "@aws-sdk/s3-request-presigner": "3", - "@ironfish/rust-nodejs": "1.4.0", - "@ironfish/sdk": "1.5.0", + "@ironfish/rust-nodejs": "1.5.0", + "@ironfish/sdk": "1.6.0", "@oclif/core": "1.23.1", "@oclif/plugin-help": "5.1.12", "@oclif/plugin-not-found": "2.3.1", diff --git a/ironfish-rust-nodejs/npm/darwin-arm64/package.json b/ironfish-rust-nodejs/npm/darwin-arm64/package.json index 1691ea5b7d..b58d2e1c2b 100644 --- a/ironfish-rust-nodejs/npm/darwin-arm64/package.json +++ b/ironfish-rust-nodejs/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-darwin-arm64", - "version": "1.4.0", + "version": "1.5.0", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/darwin-x64/package.json b/ironfish-rust-nodejs/npm/darwin-x64/package.json index c634bf94d2..acb4c38aef 100644 --- a/ironfish-rust-nodejs/npm/darwin-x64/package.json +++ b/ironfish-rust-nodejs/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-darwin-x64", - "version": "1.4.0", + "version": "1.5.0", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json b/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json index 2e9b665d06..1eeca3510a 100644 --- a/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json +++ b/ironfish-rust-nodejs/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-arm64-gnu", - "version": "1.4.0", + "version": "1.5.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json b/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json index 1438eb84d3..6c71d0f8da 100644 --- a/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json +++ b/ironfish-rust-nodejs/npm/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-arm64-musl", - "version": "1.4.0", + "version": "1.5.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json b/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json index b482db5d4a..0a0190262c 100644 --- a/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json +++ b/ironfish-rust-nodejs/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-x64-gnu", - "version": "1.4.0", + "version": "1.5.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/linux-x64-musl/package.json b/ironfish-rust-nodejs/npm/linux-x64-musl/package.json index 9be62404af..f2e7777a86 100644 --- a/ironfish-rust-nodejs/npm/linux-x64-musl/package.json +++ b/ironfish-rust-nodejs/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-linux-x64-musl", - "version": "1.4.0", + "version": "1.5.0", "os": [ "linux" ], diff --git a/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json b/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json index 5cd156777f..8f0471f3af 100644 --- a/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json +++ b/ironfish-rust-nodejs/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs-win32-x64-msvc", - "version": "1.4.0", + "version": "1.5.0", "os": [ "win32" ], diff --git a/ironfish-rust-nodejs/package.json b/ironfish-rust-nodejs/package.json index 753d101d0c..f890d33488 100644 --- a/ironfish-rust-nodejs/package.json +++ b/ironfish-rust-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs", - "version": "1.4.0", + "version": "1.5.0", "description": "Node.js bindings for Rust code required by the Iron Fish SDK", "main": "index.js", "types": "index.d.ts", diff --git a/ironfish/package.json b/ironfish/package.json index 546ab6b789..ab24263788 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/sdk", - "version": "1.5.0", + "version": "1.6.0", "description": "SDK for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -22,7 +22,7 @@ "dependencies": { "@ethersproject/bignumber": "5.7.0", "@fast-csv/format": "4.3.5", - "@ironfish/rust-nodejs": "1.4.0", + "@ironfish/rust-nodejs": "1.5.0", "@napi-rs/blake-hash": "1.3.3", "axios": "0.21.4", "bech32": "2.0.0", diff --git a/simulator/package.json b/simulator/package.json index 7ba522ee4a..c029024f46 100644 --- a/simulator/package.json +++ b/simulator/package.json @@ -56,7 +56,7 @@ "docs:open": "open docs/index.html" }, "dependencies": { - "@ironfish/sdk": "1.5.0", + "@ironfish/sdk": "1.6.0", "@oclif/core": "1.23.1", "@oclif/plugin-help": "5.1.12", "@oclif/plugin-not-found": "2.3.1",