diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80fbe99e37..0cf0b6f102 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: Node.js CI on: pull_request: branches: - - '*' + - '**' push: branches: - master diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index ca601c5a55..5a5f9b2524 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -1,6 +1,6 @@ { "name": "ironfish", - "version": "0.1.65", + "version": "0.1.66", "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/s3-request-presigner": "3.127.0", "@aws-sdk/client-cognito-identity": "3.215.0", "@aws-sdk/client-s3": "3.127.0", - "@ironfish/rust-nodejs": "0.1.25", - "@ironfish/sdk": "0.0.42", + "@ironfish/rust-nodejs": "0.1.26", + "@ironfish/sdk": "0.0.43", "@oclif/core": "1.23.1", "@oclif/plugin-help": "5.1.12", "@oclif/plugin-not-found": "2.3.1", diff --git a/ironfish-cli/src/commands/chain/power.ts b/ironfish-cli/src/commands/chain/power.ts new file mode 100644 index 0000000000..60d8afc631 --- /dev/null +++ b/ironfish-cli/src/commands/chain/power.ts @@ -0,0 +1,51 @@ +/* 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 { FileUtils } from '@ironfish/sdk' +import { parseNumber } from '../../args' +import { IronfishCommand } from '../../command' +import { LocalFlags } from '../../flags' + +export default class Power extends IronfishCommand { + static description = "Show the network's hash power (hash/s)" + + static flags = { + ...LocalFlags, + } + + static args = [ + { + name: 'blocks', + parse: (input: string): Promise => Promise.resolve(parseNumber(input)), + required: false, + description: + 'The number of blocks to look back to calculate the power. This value must be > 0', + }, + { + name: 'sequence', + parse: (input: string): Promise => Promise.resolve(parseNumber(input)), + required: false, + description: 'The sequence of the latest block from when to estimate network speed ', + }, + ] + + async start(): Promise { + const { args } = await this.parse(Power) + const inputBlocks = args.blocks as number | null | undefined + const inputSequence = args.sequence as number | null | undefined + + await this.sdk.client.connect() + + const data = await this.sdk.client.getNetworkHashPower({ + blocks: inputBlocks, + sequence: inputSequence, + }) + + const { hashesPerSecond, blocks, sequence } = data.content + const formattedHashesPerSecond = FileUtils.formatHashRate(hashesPerSecond) + + this.log( + `The network power for block ${sequence} was ${formattedHashesPerSecond} averaged over ${blocks} previous blocks.`, + ) + } +} diff --git a/ironfish-cli/src/commands/fees.ts b/ironfish-cli/src/commands/fees.ts index b0c0abd2cc..1dccfa8785 100644 --- a/ironfish-cli/src/commands/fees.ts +++ b/ironfish-cli/src/commands/fees.ts @@ -29,17 +29,17 @@ export class FeeCommand extends IronfishCommand { const feeRates = await client.estimateFeeRates() this.log('Fee Rates ($ORE/kB)') - this.log(`low: ${feeRates.content.low || ''}`) - this.log(`medium: ${feeRates.content.medium || ''}`) - this.log(`high: ${feeRates.content.high || ''}`) + this.log(`slow: ${feeRates.content.slow || ''}`) + this.log(`average: ${feeRates.content.average || ''}`) + this.log(`fast: ${feeRates.content.fast || ''}`) } async explainFeeRates(client: RpcClient): Promise { const config = await client.getConfig() - const low = config.content['feeEstimatorPercentileLow'] || '10' - const medium = config.content['feeEstimatorPercentileMedium'] || '20' - const high = config.content['feeEstimatorPercentileHigh'] || '30' + const slow = config.content['feeEstimatorPercentileLow'] || '10' + const average = config.content['feeEstimatorPercentileMedium'] || '20' + const fast = config.content['feeEstimatorPercentileHigh'] || '30' const numBlocks = config.content['feeEstimatorMaxBlockHistory'] || '10' this.log( @@ -49,9 +49,9 @@ export class FeeCommand extends IronfishCommand { 'The fee rate for each transaction is computed by dividing the transaction fee in $ORE by the size of the transaction in kB.\n', ) this.log('The low, medium, and high rates each come from a percentile in the distribution:') - this.log(`low: ${low}th`) - this.log(`medium: ${medium}th`) - this.log(`high: ${high}th`) + this.log(`slow: ${slow}th`) + this.log(`average: ${average}th`) + this.log(`fast: ${fast}th`) this.log('') } } diff --git a/ironfish-cli/src/commands/miners/pools/start.ts b/ironfish-cli/src/commands/miners/pools/start.ts index 8be93474b8..253b896436 100644 --- a/ironfish-cli/src/commands/miners/pools/start.ts +++ b/ironfish-cli/src/commands/miners/pools/start.ts @@ -9,6 +9,7 @@ import { MiningPool, parseUrl, StringUtils, + TlsUtils, WebhookNotifier, } from '@ironfish/sdk' import { Flags } from '@oclif/core' @@ -39,13 +40,14 @@ export class StartPool extends IronfishCommand { allowNo: true, description: 'Whether the pool should payout or not. Useful for solo miners', }), - balancePercentPayout: Flags.integer({ - description: 'Whether the pool should payout or not. Useful for solo miners', - }), banning: Flags.boolean({ description: 'Whether the pool should ban peers for errors or bad behavior', allowNo: true, }), + tls: Flags.boolean({ + description: 'Whether the pool should listen for connections over tls', + allowNo: true, + }), } pool: MiningPool | null = null @@ -112,6 +114,27 @@ export class StartPool extends IronfishCommand { } } + if (!host) { + host = this.sdk.config.get('poolHost') + } + + if (!port) { + port = this.sdk.config.get('poolPort') + } + + let tlsOptions = undefined + if (flags.tls) { + const fileSystem = this.sdk.fileSystem + const nodeKeyPath = this.sdk.config.get('tlsKeyPath') + const nodeCertPath = this.sdk.config.get('tlsCertPath') + tlsOptions = await TlsUtils.getTlsOptions( + fileSystem, + nodeKeyPath, + nodeCertPath, + this.logger, + ) + } + this.pool = await MiningPool.init({ config: this.sdk.config, logger: this.logger, @@ -120,8 +143,9 @@ export class StartPool extends IronfishCommand { webhooks: webhooks, host: host, port: port, - balancePercentPayoutFlag: flags.balancePercentPayout, banning: flags.banning, + tls: flags.tls, + tlsOptions: tlsOptions, }) await this.pool.start() diff --git a/ironfish-cli/src/commands/miners/pools/status.ts b/ironfish-cli/src/commands/miners/pools/status.ts index a0ce216599..ec24238bf5 100644 --- a/ironfish-cli/src/commands/miners/pools/status.ts +++ b/ironfish-cli/src/commands/miners/pools/status.ts @@ -2,13 +2,14 @@ * 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 { - createRootLogger, FileUtils, isValidPublicAddress, MiningStatusMessage, parseUrl, PromiseUtils, StratumClient, + StratumTcpClient, + StratumTlsClient, waitForEmit, } from '@ironfish/sdk' import { Flags } from '@oclif/core' @@ -33,6 +34,10 @@ export class PoolStatus extends IronfishCommand { default: false, description: 'Follow the status of the mining pool', }), + tls: Flags.boolean({ + description: 'Connect to pool over tls', + allowNo: true, + }), } async start(): Promise { @@ -57,11 +62,12 @@ export class PoolStatus extends IronfishCommand { } } - const stratum = new StratumClient({ - host: host, - port: port, - logger: createRootLogger(), - }) + let stratum: StratumClient + if (flags.tls) { + stratum = new StratumTlsClient({ host, port, logger: this.logger }) + } else { + stratum = new StratumTcpClient({ host, port, logger: this.logger }) + } if (!flags.follow) { stratum.onConnected.on(() => stratum.getStatus(flags.address)) diff --git a/ironfish-cli/src/commands/miners/start.ts b/ironfish-cli/src/commands/miners/start.ts index 9bc93bff1e..9707ddb529 100644 --- a/ironfish-cli/src/commands/miners/start.ts +++ b/ironfish-cli/src/commands/miners/start.ts @@ -9,6 +9,8 @@ import { MiningSoloMiner, parseUrl, SetIntervalToken, + StratumTcpClient, + StratumTlsClient, } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' import dns from 'dns' @@ -46,6 +48,10 @@ export class Miner extends IronfishCommand { allowNo: true, description: 'Enable fancy hashpower display', }), + tls: Flags.boolean({ + description: 'Connect to pool over tls', + allowNo: true, + }), } async start(): Promise { @@ -99,13 +105,19 @@ export class Miner extends IronfishCommand { `Starting to mine with public address: ${publicAddress} at pool ${host}:${port}${nameInfo}`, ) + let stratum + if (flags.tls) { + stratum = new StratumTlsClient({ host, port, logger: this.logger }) + } else { + stratum = new StratumTcpClient({ host, port, logger: this.logger }) + } + const miner = new MiningPoolMiner({ threadCount: flags.threads, publicAddress, logger: this.logger, batchSize, - host: host, - port: port, + stratum, name: flags.name, }) diff --git a/ironfish-cli/src/commands/peers/banned.ts b/ironfish-cli/src/commands/peers/banned.ts new file mode 100644 index 0000000000..fb29eedcfa --- /dev/null +++ b/ironfish-cli/src/commands/peers/banned.ts @@ -0,0 +1,89 @@ +/* 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 { BannedPeerResponse, GetBannedPeersResponse, PromiseUtils } from '@ironfish/sdk' +import { CliUx, Flags } from '@oclif/core' +import blessed from 'blessed' +import { IronfishCommand } from '../../command' +import { RemoteFlags } from '../../flags' + +const tableFlags = CliUx.ux.table.flags() + +export class BannedCommand extends IronfishCommand { + static description = `List all banned peers` + + static flags = { + ...RemoteFlags, + ...tableFlags, + follow: Flags.boolean({ + char: 'f', + default: false, + description: 'Follow the banned peers list live', + }), + } + + async start(): Promise { + const { flags } = await this.parse(BannedCommand) + + if (!flags.follow) { + await this.sdk.client.connect() + const response = await this.sdk.client.getBannedPeers() + this.log(renderTable(response.content)) + this.exit(0) + } + + // Console log will create display issues with Blessed + this.logger.pauseLogs() + + const screen = blessed.screen({ smartCSR: true, fullUnicode: true }) + const text = blessed.text() + screen.append(text) + + // eslint-disable-next-line no-constant-condition + while (true) { + const connected = await this.sdk.client.tryConnect() + if (!connected) { + text.clearBaseLine(0) + text.setContent('Connecting...') + screen.render() + await PromiseUtils.sleep(1000) + continue + } + + const response = this.sdk.client.getBannedPeersStream() + + for await (const value of response.contentStream()) { + text.clearBaseLine(0) + text.setContent(renderTable(value)) + screen.render() + } + } + } +} + +function renderTable(content: GetBannedPeersResponse): string { + const columns: CliUx.Table.table.Columns = { + identity: { + minWidth: 45, + header: 'IDENTITY', + get: (row) => { + return row.identity + }, + }, + reason: { + minWidth: 15, + header: 'BAN REASON', + get: (row) => { + return row.reason + }, + }, + } + + let result = '' + + CliUx.ux.table(content.peers, columns, { + printLine: (line) => (result += `${String(line)}\n`), + }) + + return result +} diff --git a/ironfish-cli/src/commands/service/estimate-fee-rates.ts b/ironfish-cli/src/commands/service/estimate-fee-rates.ts index 981385bca2..f50208971f 100644 --- a/ironfish-cli/src/commands/service/estimate-fee-rates.ts +++ b/ironfish-cli/src/commands/service/estimate-fee-rates.ts @@ -62,12 +62,12 @@ export default class EstimateFees extends IronfishCommand { const response = await this.sdk.client.estimateFeeRates() - if (!(response.content.low && response.content.medium && response.content.high)) { + if (!(response.content.slow && response.content.average && response.content.fast)) { this.log('Unexpected response') } else { - const feeRateLow = Number(CurrencyUtils.decode(response.content.low)) - const feeRateMedium = Number(CurrencyUtils.decode(response.content.medium)) - const feeRateHigh = Number(CurrencyUtils.decode(response.content.high)) + const feeRateSlow = Number(CurrencyUtils.decode(response.content.slow)) + const feeRateAverage = Number(CurrencyUtils.decode(response.content.average)) + const feeRateFast = Number(CurrencyUtils.decode(response.content.fast)) await api.submitTelemetry({ points: [ @@ -76,19 +76,19 @@ export default class EstimateFees extends IronfishCommand { timestamp: new Date(), fields: [ { - name: `fee_rate_low`, + name: `fee_rate_slow`, type: 'integer', - value: feeRateLow, + value: feeRateSlow, }, { - name: `fee_rate_medium`, + name: `fee_rate_average`, type: 'integer', - value: feeRateMedium, + value: feeRateAverage, }, { - name: `fee_rate_high`, + name: `fee_rate_fast`, type: 'integer', - value: feeRateHigh, + value: feeRateFast, }, ], tags: [{ name: 'version', value: IronfishCliPKG.version }], diff --git a/ironfish-cli/src/commands/service/faucet.ts b/ironfish-cli/src/commands/service/faucet.ts index 3148951c8f..fe63564887 100644 --- a/ironfish-cli/src/commands/service/faucet.ts +++ b/ironfish-cli/src/commands/service/faucet.ts @@ -185,7 +185,7 @@ export default class Faucet extends IronfishCommand { await api.startFaucetTransaction(faucetTransaction.id) } - const receives = faucetTransactions.map((ft) => { + const outputs = faucetTransactions.map((ft) => { return { publicAddress: ft.public_key, amount: BigInt(FAUCET_AMOUNT).toString(), @@ -196,7 +196,7 @@ export default class Faucet extends IronfishCommand { const tx = await client.sendTransaction({ fromAccountName: account, - receives, + outputs, fee: BigInt(faucetTransactions.length * FAUCET_FEE).toString(), }) diff --git a/ironfish-cli/src/commands/wallet/assets.ts b/ironfish-cli/src/commands/wallet/assets.ts new file mode 100644 index 0000000000..c32cbaa2bf --- /dev/null +++ b/ironfish-cli/src/commands/wallet/assets.ts @@ -0,0 +1,98 @@ +/* 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 { + ASSET_ID_LENGTH, + ASSET_METADATA_LENGTH, + ASSET_NAME_LENGTH, + PUBLIC_ADDRESS_LENGTH, +} from '@ironfish/rust-nodejs' +import { BufferUtils } from '@ironfish/sdk' +import { CliUx } from '@oclif/core' +import { IronfishCommand } from '../../command' +import { RemoteFlags } from '../../flags' +import { TableCols } from '../../utils/table' + +const MAX_ASSET_METADATA_COLUMN_WIDTH = ASSET_METADATA_LENGTH + 1 +const MIN_ASSET_METADATA_COLUMN_WIDTH = ASSET_METADATA_LENGTH / 2 + 1 + +const MAX_ASSET_NAME_COLUMN_WIDTH = ASSET_NAME_LENGTH + 1 +const MIN_ASSET_NAME_COLUMN_WIDTH = ASSET_NAME_LENGTH / 2 + 1 + +export class AssetsCommand extends IronfishCommand { + static description = `Display the wallet's assets` + + static flags = { + ...RemoteFlags, + ...CliUx.ux.table.flags(), + } + + static args = [ + { + name: 'account', + parse: (input: string): Promise => Promise.resolve(input.trim()), + required: false, + description: 'Name of the account', + }, + ] + + async start(): Promise { + const { flags, args } = await this.parse(AssetsCommand) + const account = args.account as string | undefined + + const client = await this.sdk.connectRpc() + const response = client.getAssets({ + account, + }) + + const assetMetadataWidth = flags.extended + ? MAX_ASSET_METADATA_COLUMN_WIDTH + : MIN_ASSET_METADATA_COLUMN_WIDTH + const assetNameWidth = flags.extended + ? MAX_ASSET_NAME_COLUMN_WIDTH + : MIN_ASSET_NAME_COLUMN_WIDTH + let showHeader = true + + for await (const asset of response.contentStream()) { + CliUx.ux.table( + [asset], + { + name: TableCols.fixedWidth({ + header: 'Name', + width: assetNameWidth, + get: (row) => BufferUtils.toHuman(Buffer.from(row.name, 'hex')), + }), + id: { + header: 'ID', + minWidth: ASSET_ID_LENGTH + 1, + }, + metadata: TableCols.fixedWidth({ + header: 'Metadata', + width: assetMetadataWidth, + get: (row) => BufferUtils.toHuman(Buffer.from(row.metadata, 'hex')), + }), + status: { + header: 'Status', + minWidth: 12, + }, + supply: { + header: 'Supply', + minWidth: 16, + get: (row) => row.supply ?? 'NULL', + }, + owner: { + header: 'Owner', + minWidth: PUBLIC_ADDRESS_LENGTH + 1, + }, + }, + { + printLine: this.log.bind(this), + ...flags, + 'no-header': !showHeader, + }, + ) + + showHeader = false + } + } +} diff --git a/ironfish-cli/src/commands/wallet/burn.ts b/ironfish-cli/src/commands/wallet/burn.ts index d47f98af62..133bb114bd 100644 --- a/ironfish-cli/src/commands/wallet/burn.ts +++ b/ironfish-cli/src/commands/wallet/burn.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 { + BufferUtils, CreateTransactionRequest, CreateTransactionResponse, CurrencyUtils, @@ -131,7 +132,7 @@ export class Burn extends IronfishCommand { fee = CurrencyUtils.encode(flags.fee) const createResponse = await client.createTransaction({ sender: account, - receives: [], + outputs: [], burns: [ { assetId, @@ -145,11 +146,11 @@ export class Burn extends IronfishCommand { rawTransactionResponse = createResponse.content.transaction } else { const feeRatesResponse = await client.estimateFeeRates() - const feeRates = new Set([ - feeRatesResponse.content.low ?? '1', - feeRatesResponse.content.medium ?? '1', - feeRatesResponse.content.high ?? '1', - ]) + const feeRates = [ + feeRatesResponse.content.slow ?? '1', + feeRatesResponse.content.average ?? '1', + feeRatesResponse.content.fast ?? '1', + ] const feeRateNames = Object.getOwnPropertyNames(feeRatesResponse.content) @@ -157,7 +158,7 @@ export class Burn extends IronfishCommand { const createTransactionRequest: CreateTransactionRequest = { sender: account, - receives: [], + outputs: [], burns: [ { assetId, @@ -270,8 +271,12 @@ ${CurrencyUtils.renderIron( const transactionBytes = Buffer.from(result.content.transaction, 'hex') transaction = new Transaction(transactionBytes) + const assetResponse = await client.getAsset({ id: assetId }) + const assetName = BufferUtils.toHuman(Buffer.from(assetResponse.content.name, 'hex')) + this.log(` -Burned asset ${assetId} from ${account} +Burned asset ${assetName} from ${account} +Asset Identifier: ${assetId} Value: ${CurrencyUtils.renderIron(amount)} Transaction Hash: ${transaction.hash().toString('hex')} diff --git a/ironfish-cli/src/commands/wallet/export.ts b/ironfish-cli/src/commands/wallet/export.ts index 6cff50723e..77a55d624c 100644 --- a/ironfish-cli/src/commands/wallet/export.ts +++ b/ironfish-cli/src/commands/wallet/export.ts @@ -2,9 +2,8 @@ * 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 { ErrorUtils } from '@ironfish/sdk' +import { Bech32m, ErrorUtils } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' -import { bech32m } from 'bech32' import fs from 'fs' import jsonColorizer from 'json-colorizer' import path from 'path' @@ -41,6 +40,10 @@ export class ExportCommand extends IronfishCommand { default: false, description: 'Output the account as JSON, rather than the default bech32', }), + path: Flags.string({ + description: 'The path to export the account to', + required: false, + }), } static args = [ @@ -50,51 +53,48 @@ export class ExportCommand extends IronfishCommand { required: false, description: 'Name of the account to export', }, - { - name: 'path', - parse: (input: string): Promise => Promise.resolve(input.trim()), - required: false, - description: 'The path to export the account to', - }, ] async start(): Promise { const { flags, args } = await this.parse(ExportCommand) const { color, local } = flags const account = args.account as string - const exportPath = args.path as string | undefined + const exportPath = flags.path + + if (flags.language) { + flags.mnemonic = true + } const client = await this.sdk.connectRpc(local) const response = await client.exportAccount({ account }) - const responseJSONString = JSON.stringify(response.content.account) let output - if (flags.language) { - output = spendingKeyToWords( - response.content.account.spendingKey, - LANGUAGES[flags.language], - ) - } else if (flags.mnemonic) { - let languageCode = inferLanguageCode() - if (languageCode !== null) { - CliUx.ux.info(`Detected Language as '${languageCodeToKey(languageCode)}', exporting:`) - } else { + + if (flags.mnemonic) { + let languageCode = flags.language ? LANGUAGES[flags.language] : null + + if (languageCode == null) { + languageCode = inferLanguageCode() + + if (languageCode !== null) { + CliUx.ux.info(`Detected Language as '${languageCodeToKey(languageCode)}', exporting:`) + } + } + + if (languageCode == null) { CliUx.ux.info(`Could not detect your language, please select language for export`) languageCode = await selectLanguage() } + output = spendingKeyToWords(response.content.account.spendingKey, languageCode) } else if (flags.json) { - output = exportPath - ? JSON.stringify(response.content.account, undefined, ' ') - : responseJSONString + output = JSON.stringify(response.content.account, undefined, ' ') + + if (color && flags.json && !exportPath) { + output = jsonColorizer(output) + } } else { - const responseBytes = Buffer.from(responseJSONString) - const lengthLimit = 1023 - output = bech32m.encode( - 'ironfishaccount00000', - bech32m.toWords(responseBytes), - lengthLimit, - ) + output = Bech32m.encode(JSON.stringify(response.content.account), 'ironfishaccount00000') } if (exportPath) { @@ -133,9 +133,6 @@ export class ExportCommand extends IronfishCommand { return } - if (color && flags.json) { - output = jsonColorizer(output) - } this.log(output) } } diff --git a/ironfish-cli/src/commands/wallet/import.ts b/ironfish-cli/src/commands/wallet/import.ts index de1dfe1f02..d8d6ee856f 100644 --- a/ironfish-cli/src/commands/wallet/import.ts +++ b/ironfish-cli/src/commands/wallet/import.ts @@ -1,10 +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 { wordsToSpendingKey } from '@ironfish/rust-nodejs' -import { AccountImport, JSONUtils, PromiseUtils } from '@ironfish/sdk' +import { generateKeyFromPrivateKey, wordsToSpendingKey } from '@ironfish/rust-nodejs' +import { AccountImport, Bech32m, JSONUtils, PromiseUtils } from '@ironfish/sdk' import { CliUx, Flags } from '@oclif/core' -import { bech32m } from 'bech32' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' import { LANGUAGE_VALUES } from '../../utils/language' @@ -19,25 +18,52 @@ export class ImportCommand extends IronfishCommand { default: true, description: 'Rescan the blockchain once the account is imported', }), + path: Flags.string({ + description: 'the path to the file containing the account to import', + flagName: 'path', + }), } static args = [ { - name: 'path', + name: 'blob', parse: (input: string): Promise => Promise.resolve(input.trim()), required: false, - description: 'The path to import the account from', + description: 'The copy-pasted output of wallet:export', }, ] - static bech32ToJSON(bech32: string): string | null { - try { - const decodedOutput = bech32m.decode(bech32, 1023) - const decodedWords = decodedOutput.words - const decodedBytes = bech32m.fromWords(decodedWords) - return Buffer.from(decodedBytes).toString() - } catch (e) { - return null + async start(): Promise { + const { flags, args } = await this.parse(ImportCommand) + const blob = args.blob as string | undefined + + const client = await this.sdk.connectRpc() + + let account: AccountImport + if (blob) { + account = await this.stringToAccountImport(blob) + } else if (flags.path) { + account = await this.importFile(flags.path) + } else if (process.stdin.isTTY) { + account = await this.importTTY() + } else if (!process.stdin.isTTY) { + account = await this.importPipe() + } else { + CliUx.ux.error(`Invalid import type`) + } + + const result = await client.importAccount({ + account, + rescan: flags.rescan, + }) + + const { name, isDefaultAccount } = result.content + this.log(`Account ${name} imported.`) + + if (isDefaultAccount) { + this.log(`The default account is now: ${name}`) + } else { + this.log(`Run "ironfish wallet:use ${name}" to set the account as default`) } } @@ -61,64 +87,37 @@ export class ImportCommand extends IronfishCommand { ) } - async start(): Promise { - const { flags, args } = await this.parse(ImportCommand) - const importPath = args.path as string | undefined - - const client = await this.sdk.connectRpc() - - let account: AccountImport | null = null - if (importPath) { - account = await this.importFile(importPath) - } else if (process.stdin.isTTY) { - account = await this.importTTY() - } else if (!process.stdin.isTTY) { - account = await this.importPipe() + static verifySpendingKey(spendingKey: string): string | null { + try { + return generateKeyFromPrivateKey(spendingKey)?.spending_key + } catch (e) { + return null } + } - if (account === null) { - this.log('No account to import provided') - return this.exit(1) + async stringToAccountImport(data: string): Promise { + // bech32 encoded json + const [decoded, _] = Bech32m.decode(data) + if (decoded) { + return JSONUtils.parse(decoded) } - const result = await client.importAccount({ - account: account, - rescan: flags.rescan, - }) - - const { name, isDefaultAccount } = result.content - this.log(`Account ${name} imported.`) + // mnemonic or explicit spending key + const spendingKey = + ImportCommand.mnemonicWordsToKey(data) || ImportCommand.verifySpendingKey(data) - if (isDefaultAccount) { - this.log(`The default account is now: ${name}`) - } else { - this.log(`Run "ironfish wallet:use ${name}" to set the account as default`) - } - } - async stringToAccountImport(data: string): Promise { - // try bech32 first - const bech32 = ImportCommand.bech32ToJSON(data) - if (bech32) { - return JSONUtils.parse(bech32) - } - // then try mnemonic - const spendingKey = ImportCommand.mnemonicWordsToKey(data) if (spendingKey) { - const name = await CliUx.ux.prompt('Enter the account name', { + const name = await CliUx.ux.prompt('Enter a new account name', { required: true, }) - return { - name, - spendingKey, - } + return { name, spendingKey } } - // last try json + + // raw json try { return JSONUtils.parse(data) } catch (e) { - throw new Error( - 'Could not detect a valid account format, please verify your account info input', - ) + CliUx.ux.error(`Import failed for the given input: ${data}`) } } @@ -151,28 +150,7 @@ export class ImportCommand extends IronfishCommand { const userInput = await CliUx.ux.prompt('Paste the output of wallet:export', { required: true, }) - try { - return this.stringToAccountImport(userInput) - } catch (e) { - CliUx.ux.error( - 'Failed to decode the account from the provided input, please continue with the manual input below', - { - exit: false, - }, - ) - } - - const accountName = await CliUx.ux.prompt('Enter the account name', { - required: true, - }) - const spendingKey = await CliUx.ux.prompt('Enter the account spending key', { - required: true, - }) - - return { - name: accountName, - spendingKey: spendingKey, - } + return await this.stringToAccountImport(userInput) } } diff --git a/ironfish-cli/src/commands/wallet/mint.ts b/ironfish-cli/src/commands/wallet/mint.ts index f8587f80ba..643cb85d30 100644 --- a/ironfish-cli/src/commands/wallet/mint.ts +++ b/ironfish-cli/src/commands/wallet/mint.ts @@ -174,7 +174,7 @@ export class Mint extends IronfishCommand { const createResponse = await client.createTransaction({ sender: account, - receives: [], + outputs: [], mints: [ { assetId, @@ -190,11 +190,11 @@ export class Mint extends IronfishCommand { rawTransactionResponse = createResponse.content.transaction } else { const feeRatesResponse = await client.estimateFeeRates() - const feeRates = new Set([ - feeRatesResponse.content.low ?? '1', - feeRatesResponse.content.medium ?? '1', - feeRatesResponse.content.high ?? '1', - ]) + const feeRates = [ + feeRatesResponse.content.slow ?? '1', + feeRatesResponse.content.average ?? '1', + feeRatesResponse.content.fast ?? '1', + ] const feeRateNames = Object.getOwnPropertyNames(feeRatesResponse.content) @@ -202,7 +202,7 @@ export class Mint extends IronfishCommand { const createTransactionRequest: CreateTransactionRequest = { sender: account, - receives: [], + outputs: [], mints: [ { assetId, diff --git a/ironfish-cli/src/commands/wallet/notes.ts b/ironfish-cli/src/commands/wallet/notes.ts index 17ae2eea60..9cdbefeea8 100644 --- a/ironfish-cli/src/commands/wallet/notes.ts +++ b/ironfish-cli/src/commands/wallet/notes.ts @@ -1,16 +1,18 @@ /* 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 { BufferUtils, CurrencyUtils } from '@ironfish/sdk' +import { CurrencyUtils } from '@ironfish/sdk' import { CliUx } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' +import { TableCols } from '../../utils/table' export class NotesCommand extends IronfishCommand { static description = `Display the account notes` static flags = { ...RemoteFlags, + ...CliUx.ux.table.flags(), } static args = [ @@ -23,7 +25,7 @@ export class NotesCommand extends IronfishCommand { ] async start(): Promise { - const { args } = await this.parse(NotesCommand) + const { flags, args } = await this.parse(NotesCommand) const account = args.account as string | undefined const client = await this.sdk.connectRpc() @@ -36,17 +38,6 @@ export class NotesCommand extends IronfishCommand { CliUx.ux.table( [note], { - value: { - header: 'Amount', - get: (row) => CurrencyUtils.renderIron(row.value), - }, - assetName: { - header: 'Asset Name', - get: (row) => BufferUtils.toHuman(Buffer.from(row.assetName, 'hex')), - }, - assetId: { - header: 'Asset Id', - }, memo: { header: 'Memo', // Maximum memo length is 32 bytes @@ -64,12 +55,18 @@ export class NotesCommand extends IronfishCommand { if (row.spent === undefined) { return '-' } else { - return row.spent ? `✔` : `x` + return row.spent ? `✔` : `` } }, }, + ...TableCols.asset({ extended: flags.extended }), + value: { + header: 'Amount', + get: (row) => CurrencyUtils.renderIron(row.value), + minWidth: 16, + }, }, - { 'no-header': !showHeader }, + { ...flags, 'no-header': !showHeader }, ) showHeader = false } diff --git a/ironfish-cli/src/commands/wallet/post.ts b/ironfish-cli/src/commands/wallet/post.ts index 467351e3a4..050305e3a0 100644 --- a/ironfish-cli/src/commands/wallet/post.ts +++ b/ironfish-cli/src/commands/wallet/post.ts @@ -114,8 +114,8 @@ export class PostCommand extends IronfishCommand { confirm(raw: RawTransaction, account: string): Promise { let spending = 0n - for (const recieve of raw.receives) { - spending += recieve.note.value() + for (const output of raw.outputs) { + spending += output.note.value() } this.log( diff --git a/ironfish-cli/src/commands/wallet/send.ts b/ironfish-cli/src/commands/wallet/send.ts index 80eb59d97d..afde1be30e 100644 --- a/ironfish-cli/src/commands/wallet/send.ts +++ b/ironfish-cli/src/commands/wallet/send.ts @@ -188,11 +188,11 @@ export class Send extends IronfishCommand { let rawTransactionResponse: string if (fee === null && feeRate === null) { const feeRatesResponse = await client.estimateFeeRates() - const feeRates = new Set([ - feeRatesResponse.content.low ?? '1', - feeRatesResponse.content.medium ?? '1', - feeRatesResponse.content.high ?? '1', - ]) + const feeRates = [ + feeRatesResponse.content.slow ?? '1', + feeRatesResponse.content.average ?? '1', + feeRatesResponse.content.fast ?? '1', + ] const feeRateNames = Object.getOwnPropertyNames(feeRatesResponse.content) @@ -200,7 +200,7 @@ export class Send extends IronfishCommand { const createTransactionRequest: CreateTransactionRequest = { sender: from, - receives: [ + outputs: [ { publicAddress: to, amount: CurrencyUtils.encode(amount), @@ -246,7 +246,7 @@ export class Send extends IronfishCommand { } else { const createResponse = await client.createTransaction({ sender: from, - receives: [ + outputs: [ { publicAddress: to, amount: CurrencyUtils.encode(amount), diff --git a/ironfish-cli/src/commands/wallet/transactions.ts b/ironfish-cli/src/commands/wallet/transactions.ts index 2af5765c15..06bdb54307 100644 --- a/ironfish-cli/src/commands/wallet/transactions.ts +++ b/ironfish-cli/src/commands/wallet/transactions.ts @@ -1,10 +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 { Asset, ASSET_NAME_LENGTH } from '@ironfish/rust-nodejs' +import { Asset } from '@ironfish/rust-nodejs' import { Assert, - BufferUtils, CurrencyUtils, GetAccountTransactionsResponse, PartialRecursive, @@ -13,10 +12,7 @@ import { import { CliUx, Flags } from '@oclif/core' import { IronfishCommand } from '../../command' import { RemoteFlags } from '../../flags' -import { TableCols, truncateCol } from '../../utils/table' - -const MAX_ASSET_NAME_COLUMN_WIDTH = ASSET_NAME_LENGTH + 1 -const MIN_ASSET_NAME_COLUMN_WIDTH = ASSET_NAME_LENGTH / 2 + 1 +import { TableCols } from '../../utils/table' export class TransactionsCommand extends IronfishCommand { static description = `Display the account transactions` @@ -108,7 +104,7 @@ export class TransactionsCommand extends IronfishCommand { ...transaction, group: isGroup ? '┏' : '', assetId: nativeAssetId, - assetName: '$IRON', + assetName: Buffer.from('$IRON').toString('hex'), amount, feePaid, }) @@ -128,8 +124,8 @@ export class TransactionsCommand extends IronfishCommand { transactionRows.push({ ...transaction, group: assetCount === 2 ? '' : '┏', - assetId: assetId, - assetName: BufferUtils.toHuman(Buffer.from(assetName, 'hex')), + assetId, + assetName, amount: BigInt(delta), feePaid, }) @@ -137,7 +133,7 @@ export class TransactionsCommand extends IronfishCommand { transactionRows.push({ group: index === assetCount - 1 ? '┗' : '┣', assetId, - assetName: BufferUtils.toHuman(Buffer.from(assetName, 'hex')), + assetName, amount: BigInt(delta), }) } @@ -147,9 +143,7 @@ export class TransactionsCommand extends IronfishCommand { } getColumns(extended: boolean): CliUx.Table.table.Columns> { - const assetNameWidth = extended ? MAX_ASSET_NAME_COLUMN_WIDTH : MIN_ASSET_NAME_COLUMN_WIDTH - - let columns: CliUx.Table.table.Columns = { + return { group: { header: '', minWidth: 3, @@ -197,44 +191,7 @@ export class TransactionsCommand extends IronfishCommand { get: (row) => row.feePaid && row.feePaid !== 0n ? CurrencyUtils.renderIron(row.feePaid) : '', }, - } - - if (extended) { - columns = { - ...columns, - assetId: { - header: 'Asset ID', - extended: true, - }, - assetName: { - header: 'Asset Name', - get: (row) => { - Assert.isNotUndefined(row.assetName) - return truncateCol(row.assetName, assetNameWidth) - }, - minWidth: assetNameWidth, - extended: true, - }, - } - } else { - columns = { - ...columns, - asset: { - header: 'Asset', - get: (row) => { - Assert.isNotUndefined(row.assetName) - Assert.isNotUndefined(row.assetId) - const assetName = truncateCol(row.assetName, assetNameWidth) - const text = assetName.padEnd(assetNameWidth, ' ') - return `${text} (${row.assetId.slice(0, 5)})` - }, - minWidth: assetNameWidth, - }, - } - } - - return { - ...columns, + ...TableCols.asset({ extended }), amount: { header: 'Net Amount', get: (row) => { diff --git a/ironfish-cli/src/utils/asset.ts b/ironfish-cli/src/utils/asset.ts index 8b903fc4ef..942d639515 100644 --- a/ironfish-cli/src/utils/asset.ts +++ b/ironfish-cli/src/utils/asset.ts @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Asset } from '@ironfish/rust-nodejs' -import { BufferUtils, RpcClient } from '@ironfish/sdk' +import { BufferUtils, CurrencyUtils, RpcClient } from '@ironfish/sdk' import inquirer from 'inquirer' export async function selectAsset( @@ -38,14 +38,14 @@ export async function selectAsset( } // Get the asset name from the chain DB to populate the display choices - for (const { assetId } of balances) { + for (const { assetId, confirmed } of balances) { const assetResponse = await client.getAsset({ id: assetId }) if (assetResponse.content.name) { const displayName = BufferUtils.toHuman(Buffer.from(assetResponse.content.name, 'hex')) assetOptions.push({ value: assetId, - name: `${assetId} (${displayName})`, + name: `${assetId} (${displayName}) (${CurrencyUtils.renderIron(confirmed)})`, }) } } diff --git a/ironfish-cli/src/utils/table.ts b/ironfish-cli/src/utils/table.ts index fb22c113a8..673866fbc3 100644 --- a/ironfish-cli/src/utils/table.ts +++ b/ironfish-cli/src/utils/table.ts @@ -2,7 +2,8 @@ * 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 { TimeUtils } from '@ironfish/sdk' +import { ASSET_NAME_LENGTH } from '@ironfish/rust-nodejs' +import { Assert, BufferUtils, TimeUtils } from '@ironfish/sdk' import { table } from '@oclif/core/lib/cli-ux/styled/table' /** @@ -17,6 +18,9 @@ const MAX_TIMESTAMP_LENGTH = TimeUtils.renderString( new Date(2024, 11, 25, 23, 59, 59).getTime(), ).length +const MAX_ASSET_NAME_COLUMN_WIDTH = ASSET_NAME_LENGTH + 1 +const MIN_ASSET_NAME_COLUMN_WIDTH = ASSET_NAME_LENGTH / 2 + 1 + const timestamp = >(options?: { streaming?: boolean header?: string @@ -55,12 +59,68 @@ const timestamp = >(options?: { } } -export const TableCols = { timestamp } +const asset = >(options?: { + extended?: boolean +}): Partial>> => { + if (options?.extended) { + return { + assetId: { + header: 'Asset ID', + get: (row) => row['assetId'], + minWidth: MAX_ASSET_NAME_COLUMN_WIDTH, + extended: true, + }, + assetName: { + header: 'Asset Name', + get: (row) => { + Assert.isString(row.assetName) + const assetName = BufferUtils.toHuman(Buffer.from(row.assetName, 'hex')) + return truncateCol(assetName, MAX_ASSET_NAME_COLUMN_WIDTH) + }, + minWidth: MAX_ASSET_NAME_COLUMN_WIDTH, + extended: true, + }, + } + } else { + return { + asset: { + header: 'Asset', + get: (row) => { + Assert.isString(row.assetName) + Assert.isString(row.assetId) + const assetName = truncateCol( + BufferUtils.toHuman(Buffer.from(row.assetName, 'hex')), + MIN_ASSET_NAME_COLUMN_WIDTH, + ) + const text = assetName.padEnd(MIN_ASSET_NAME_COLUMN_WIDTH, ' ') + return `${text} (${row.assetId.slice(0, 5)})` + }, + minWidth: MIN_ASSET_NAME_COLUMN_WIDTH, + extended: false, + }, + } + } +} -export function truncateCol(value: string, maxWidth: number | null): string { +const fixedWidth = >(options: { + width: number + get: (row: T) => string + header?: string + extended?: boolean +}): Partial> => { + return { + ...options, + get: (row) => truncateCol(options.get(row), options.width), + minWidth: options.width, + } +} + +function truncateCol(value: string, maxWidth: number | null): string { if (maxWidth === null || value.length <= maxWidth) { return value } return value.slice(0, maxWidth - 1) + '…' } + +export const TableCols = { timestamp, asset, fixedWidth } diff --git a/ironfish-mpc/README.md b/ironfish-mpc/README.md index b846ff7b37..a38d4ce36a 100644 --- a/ironfish-mpc/README.md +++ b/ironfish-mpc/README.md @@ -2,6 +2,10 @@ Much of the code in this folder was originally forked from https://github.com/zcash-hackworks/sapling-mpc. The original licenses and copyright are retained in this folder. +## Beacon + +Our final contribution will be seeded using the randomness generated from [The League of Entropy's drand network](https://drand.love/) in round #2759370. + ## License Licensed under either of diff --git a/ironfish-mpc/src/bin/beacon.rs b/ironfish-mpc/src/bin/beacon.rs index 6019a096d4..ff7ac617f2 100644 --- a/ironfish-mpc/src/bin/beacon.rs +++ b/ironfish-mpc/src/bin/beacon.rs @@ -35,12 +35,9 @@ fn main() { use rand::SeedableRng; use rand_chacha::ChaChaRng; - // Place beacon value here (2^42 SHA256 hash of Bitcoin block hash #534861) - let beacon_value: [u8; 32] = - decode_hex("2bf41a959668e5b9b688e58d613b3dcc99ee159a880cf764ec67e6488d8b8af3") - .as_slice() - .try_into() - .unwrap(); + // Place beacon value here. The value will be the randomness generated by The League of Entropy's drand network + // (network chain hash: 8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce) in round #2759370. + let beacon_value: [u8; 32] = decode_hex("").as_slice().try_into().unwrap(); print!("Final result of beacon: "); for b in beacon_value.iter() { diff --git a/ironfish-rust-nodejs/index.d.ts b/ironfish-rust-nodejs/index.d.ts index c1924a35f9..48660d0ec9 100644 --- a/ironfish-rust-nodejs/index.d.ts +++ b/ironfish-rust-nodejs/index.d.ts @@ -149,7 +149,7 @@ export type NativeTransaction = Transaction export class Transaction { constructor(spenderHexKey: string) /** Create a proof of a new note owned by the recipient in this transaction. */ - receive(note: Note): void + output(note: Note): void /** Spend the note owned by spender_hex_key at the given witness location. */ spend(note: Note, witness: object): void /** Mint a new asset with a given value as part of this transaction. */ diff --git a/ironfish-rust-nodejs/npm/darwin-arm64/package.json b/ironfish-rust-nodejs/npm/darwin-arm64/package.json index 7c9b01185a..a3cd00cd25 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": "0.1.25", + "version": "0.1.26", "os": [ "darwin" ], diff --git a/ironfish-rust-nodejs/npm/darwin-x64/package.json b/ironfish-rust-nodejs/npm/darwin-x64/package.json index b948d9de91..d94e6dacec 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": "0.1.25", + "version": "0.1.26", "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 60bdea6b72..5cfada4e62 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": "0.1.25", + "version": "0.1.26", "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 ea746fe6a1..d236ecb0f6 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": "0.1.25", + "version": "0.1.26", "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 bcfa4954bf..99585b4ef6 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": "0.1.25", + "version": "0.1.26", "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 132ce60924..d94f559a6e 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": "0.1.25", + "version": "0.1.26", "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 0e2502612d..f92ee4222a 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": "0.1.25", + "version": "0.1.26", "os": [ "win32" ], diff --git a/ironfish-rust-nodejs/package.json b/ironfish-rust-nodejs/package.json index 99947a49a1..a3d62fe335 100644 --- a/ironfish-rust-nodejs/package.json +++ b/ironfish-rust-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/rust-nodejs", - "version": "0.1.25", + "version": "0.1.26", "description": "Node.js bindings for Rust code required by the Iron Fish SDK", "main": "index.js", "types": "index.d.ts", diff --git a/ironfish-rust-nodejs/src/lib.rs b/ironfish-rust-nodejs/src/lib.rs index 625648b51f..f6f06bf479 100644 --- a/ironfish-rust-nodejs/src/lib.rs +++ b/ironfish-rust-nodejs/src/lib.rs @@ -80,7 +80,8 @@ pub fn generate_key() -> Key { #[napi] pub fn spending_key_to_words(private_key: String, language_code: LanguageCode) -> Result { let key = SaplingKey::from_hex(&private_key).map_err(to_napi_err)?; - key.to_words(language_code.into()).map_err(to_napi_err) + let mnemonic = key.to_words(language_code.into()).map_err(to_napi_err)?; + Ok(mnemonic.into_phrase()) } #[napi] diff --git a/ironfish-rust-nodejs/src/structs/transaction.rs b/ironfish-rust-nodejs/src/structs/transaction.rs index 5ba401045d..9bde691ef9 100644 --- a/ironfish-rust-nodejs/src/structs/transaction.rs +++ b/ironfish-rust-nodejs/src/structs/transaction.rs @@ -184,7 +184,7 @@ impl NativeTransaction { /// Create a proof of a new note owned by the recipient in this transaction. #[napi] - pub fn receive(&mut self, note: &NativeNote) -> Result<()> { + pub fn output(&mut self, note: &NativeNote) -> Result<()> { self.transaction .add_output(note.note.clone()) .map_err(to_napi_err)?; diff --git a/ironfish-rust-nodejs/tests/demo.test.slow.ts b/ironfish-rust-nodejs/tests/demo.test.slow.ts index 54eaddc308..8cb8fbe66b 100644 --- a/ironfish-rust-nodejs/tests/demo.test.slow.ts +++ b/ironfish-rust-nodejs/tests/demo.test.slow.ts @@ -53,7 +53,7 @@ describe('Demonstrate the Sapling API', () => { const transaction = new Transaction(key.spending_key) const note = new Note(key.public_address, BigInt(20), 'test', Asset.nativeId(), key.public_address) - transaction.receive(note) + transaction.output(note) const serializedPostedTransaction = transaction.post_miners_fee() const postedTransaction = new TransactionPosted(serializedPostedTransaction) @@ -90,7 +90,7 @@ describe('Demonstrate the Sapling API', () => { const minersFeeTransaction = new Transaction(key.spending_key) const minersFeeNote = new Note(key.public_address, BigInt(20), 'miner', Asset.nativeId(), key.public_address) - minersFeeTransaction.receive(minersFeeNote) + minersFeeTransaction.output(minersFeeNote) const postedMinersFeeTransaction = new TransactionPosted(minersFeeTransaction.post_miners_fee()) @@ -119,7 +119,7 @@ describe('Demonstrate the Sapling API', () => { } transaction.spend(decryptedNote, witness) - transaction.receive(newNote) + transaction.output(newNote) const postedTransaction = new TransactionPosted(transaction.post(key.public_address, BigInt(5))) diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index a5574f8894..a7b6750a55 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -29,6 +29,7 @@ pub enum IronfishError { InvalidEntropy, InvalidLanguageEncoding, InvalidMinersFeeTransaction, + InvalidMnemonicString, InvalidNonceLength, InvalidPaymentAddress, InvalidPublicAddress, diff --git a/ironfish-rust/src/keys/mod.rs b/ironfish-rust/src/keys/mod.rs index c92c260acf..f19a858b1d 100644 --- a/ironfish-rust/src/keys/mod.rs +++ b/ironfish-rust/src/keys/mod.rs @@ -185,16 +185,15 @@ impl SaplingKey { /// a seed. This isn't strictly necessary for private key, but view keys /// will need a direct mapping. The private key could still be generated /// using bip-32 and bip-39 if desired. - pub fn to_words(&self, language: Language) -> Result { - let mnemonic = Mnemonic::from_entropy(&self.spending_key, language) - .map_err(|_| IronfishError::InvalidEntropy)?; - Ok(mnemonic.phrase().to_string()) + pub fn to_words(&self, language: Language) -> Result { + Mnemonic::from_entropy(&self.spending_key, language) + .map_err(|_| IronfishError::InvalidEntropy) } /// Takes a bip-39 phrase as a string and turns it into a SaplingKey instance pub fn from_words(words: String, language: Language) -> Result { let mnemonic = Mnemonic::from_phrase(&words, language) - .map_err(|_| IronfishError::InvalidPaymentAddress)?; + .map_err(|_| IronfishError::InvalidMnemonicString)?; let bytes = mnemonic.entropy(); let mut byte_arr = [0; SPEND_KEY_SIZE]; byte_arr.clone_from_slice(&bytes[0..SPEND_KEY_SIZE]); diff --git a/ironfish-rust/src/keys/test.rs b/ironfish-rust/src/keys/test.rs index 09e22c7981..f49b4211ad 100644 --- a/ironfish-rust/src/keys/test.rs +++ b/ironfish-rust/src/keys/test.rs @@ -123,9 +123,10 @@ fn test_from_and_to_words() { // Convert to words let key = SaplingKey::new(key_bytes).expect("Key should be created"); - let words = key + let mnemonic = key .to_words(bip39::Language::English) .expect("Should return words"); + let words = mnemonic.into_phrase(); assert_eq!(words_for_bytes, words); // Convert from words diff --git a/ironfish/package.json b/ironfish/package.json index 30df5d8757..79d81b4586 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/sdk", - "version": "0.0.42", + "version": "0.0.43", "description": "SDK for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -18,7 +18,7 @@ ], "dependencies": { "@ethersproject/bignumber": "5.7.0", - "@ironfish/rust-nodejs": "0.1.25", + "@ironfish/rust-nodejs": "0.1.26", "@napi-rs/blake-hash": "1.3.1", "axios": "0.21.4", "blru": "0.1.6", diff --git a/ironfish/src/blockchain/blockchain.test.ts b/ironfish/src/blockchain/blockchain.test.ts index 7cb0a31f53..42250fbd4e 100644 --- a/ironfish/src/blockchain/blockchain.test.ts +++ b/ironfish/src/blockchain/blockchain.test.ts @@ -1404,7 +1404,7 @@ describe('Blockchain', () => { const rawSend = new RawTransaction() rawSend.spends = [{ note: note.note, witness }] - rawSend.receives = [ + rawSend.outputs = [ { note: new Note( new NativeNote( diff --git a/ironfish/src/consensus/verifier.test.ts b/ironfish/src/consensus/verifier.test.ts index 8c911d9752..818f0ed328 100644 --- a/ironfish/src/consensus/verifier.test.ts +++ b/ironfish/src/consensus/verifier.test.ts @@ -198,8 +198,8 @@ describe('Verifier', () => { owner, ) const transaction = new NativeTransaction(key.spending_key) - transaction.receive(minerNote1) - transaction.receive(minerNote2) + transaction.output(minerNote1) + transaction.output(minerNote2) return new Transaction(transaction._postMinersFeeUnchecked()) }, { diff --git a/ironfish/src/fileStores/config.ts b/ironfish/src/fileStores/config.ts index 7f5fbd6006..a91dc6b60b 100644 --- a/ironfish/src/fileStores/config.ts +++ b/ironfish/src/fileStores/config.ts @@ -13,7 +13,7 @@ export const DEFAULT_DISCORD_INVITE = 'https://discord.ironfish.network' export const DEFAULT_USE_RPC_IPC = true export const DEFAULT_USE_RPC_TCP = false export const DEFAULT_USE_RPC_TLS = true -export const DEFAULT_POOL_HOST = '0.0.0.0' +export const DEFAULT_POOL_HOST = '::' export const DEFAULT_POOL_PORT = 9034 export const DEFAULT_NETWORK_ID = 0 @@ -144,11 +144,6 @@ export type ConfigOptions = { */ poolBanning: boolean - /** - * The percent of the confirmed balance of the pool's account that it will payout - */ - poolBalancePercentPayout: number - /** * The host that the pool is listening for miner connections on. */ @@ -164,16 +159,6 @@ export type ConfigOptions = { */ poolDifficulty: string - /** - * The length of time in seconds that the pool will wait between checking if it is time to make a payout. - */ - poolAttemptPayoutInterval: number - - /** - * The length of time in seconds that the pool will wait between successful payouts. - */ - poolSuccessfulPayoutInterval: number - /** * The length of time in seconds that the pool will wait between status * messages. Setting to 0 disables status messages. @@ -185,6 +170,12 @@ export type ConfigOptions = { */ poolRecentShareCutoff: number + /** + * The length of time in seconds for each payout period. This is used to + * calculate the number of shares and how much they earn per period. + */ + poolPayoutPeriodDuration: number + /** * The discord webhook URL to post pool critical pool information to */ @@ -285,12 +276,9 @@ export const ConfigOptionsSchema: yup.ObjectSchema> = yup poolName: yup.string(), poolAccountName: yup.string(), poolBanning: yup.boolean(), - poolBalancePercentPayout: YupUtils.isPercent, poolHost: yup.string().trim(), poolPort: YupUtils.isPort, poolDifficulty: yup.string(), - poolAttemptPayoutInterval: YupUtils.isPositiveInteger, - poolSuccessfulPayoutInterval: YupUtils.isPositiveInteger, poolStatusNotificationInterval: YupUtils.isPositiveInteger, poolRecentShareCutoff: YupUtils.isPositiveInteger, poolDiscordWebhook: yup.string(), @@ -367,14 +355,12 @@ export class Config extends KeyStore { poolName: 'Iron Fish Pool', poolAccountName: 'default', poolBanning: true, - poolBalancePercentPayout: 10, poolHost: DEFAULT_POOL_HOST, poolPort: DEFAULT_POOL_PORT, poolDifficulty: '15000000000', - poolAttemptPayoutInterval: 15 * 60, // 15 minutes - poolSuccessfulPayoutInterval: 2 * 60 * 60, // 2 hours poolStatusNotificationInterval: 30 * 60, // 30 minutes poolRecentShareCutoff: 2 * 60 * 60, // 2 hours + poolPayoutPeriodDuration: 2 * 60 * 60, // 2 hours poolDiscordWebhook: '', poolMaxConnectionsPerIp: 0, poolLarkWebhook: '', diff --git a/ironfish/src/genesis/makeGenesisBlock.ts b/ironfish/src/genesis/makeGenesisBlock.ts index 8b883cedfd..dc9c243029 100644 --- a/ironfish/src/genesis/makeGenesisBlock.ts +++ b/ironfish/src/genesis/makeGenesisBlock.ts @@ -78,7 +78,7 @@ export async function makeGenesisBlock( ) const minersFeeTransaction = new NativeTransaction(minersFeeKey.spending_key) - minersFeeTransaction.receive(note) + minersFeeTransaction.output(note) const postedMinersFeeTransaction = new Transaction(minersFeeTransaction.post_miners_fee()) /** @@ -91,7 +91,7 @@ export async function makeGenesisBlock( const initialTransaction = new NativeTransaction(genesisKey.spending_key) logger.info(' Generating the output...') - initialTransaction.receive(genesisNote) + initialTransaction.output(genesisNote) logger.info(' Posting the initial transaction...') const postedInitialTransaction = new Transaction(initialTransaction.post_miners_fee()) @@ -141,7 +141,7 @@ export async function makeGenesisBlock( Asset.nativeId(), genesisNote.owner(), ) - transaction.receive(note) + transaction.output(note) } logger.info(' Posting the transaction...') diff --git a/ironfish/src/migrations/data/018-backfill-wallet-assets.ts b/ironfish/src/migrations/data/018-backfill-wallet-assets.ts new file mode 100644 index 0000000000..eb87c7febf --- /dev/null +++ b/ironfish/src/migrations/data/018-backfill-wallet-assets.ts @@ -0,0 +1,59 @@ +/* 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 { Logger } from '../../logger' +import { IronfishNode } from '../../node' +import { IDatabase, IDatabaseTransaction } from '../../storage' +import { Account } from '../../wallet' +import { Migration } from '../migration' + +export class Migration018 extends Migration { + path = __filename + + prepare(node: IronfishNode): IDatabase { + return node.wallet.walletDb.db + } + + async forward( + node: IronfishNode, + _db: IDatabase, + tx: IDatabaseTransaction | undefined, + logger: Logger, + ): Promise { + const accounts = [] + for await (const accountValue of node.wallet.walletDb.loadAccounts(tx)) { + accounts.push( + new Account({ + ...accountValue, + walletDb: node.wallet.walletDb, + }), + ) + } + + logger.info(`Backfilling assets for ${accounts.length} accounts`) + + for (const account of accounts) { + logger.info('') + logger.info(` Backfilling assets for account ${account.name}`) + + for await (const transactionValue of account.getTransactionsOrderedBySequence(tx)) { + await account.saveMintsToAssetsStore(transactionValue, tx) + await account.saveConnectedBurnsToAssetsStore(transactionValue.transaction, tx) + } + + let assetCount = 0 + for await (const _ of account.getAssets(tx)) { + assetCount++ + } + + const assetsString = assetCount === 1 ? `${assetCount} asset` : `${assetCount} : assets` + logger.info(` Completed backfilling ${assetsString} for account ${account.name}`) + } + + logger.info('') + } + + async backward(node: IronfishNode): Promise { + await node.wallet.walletDb.assets.clear() + } +} diff --git a/ironfish/src/migrations/data/019-backfill-wallet-assets-from-chain.ts b/ironfish/src/migrations/data/019-backfill-wallet-assets-from-chain.ts new file mode 100644 index 0000000000..3ae7515848 --- /dev/null +++ b/ironfish/src/migrations/data/019-backfill-wallet-assets-from-chain.ts @@ -0,0 +1,82 @@ +/* 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 { AssetValueEncoding } from '../../blockchain/database/assetValue' +import { AssetSchema } from '../../blockchain/schema' +import { Logger } from '../../logger' +import { IronfishNode } from '../../node' +import { BUFFER_ENCODING, IDatabase, IDatabaseStore, IDatabaseTransaction } from '../../storage' +import { createDB } from '../../storage/utils' +import { BufferUtils } from '../../utils' +import { Account } from '../../wallet' +import { Migration } from '../migration' + +export class Migration019 extends Migration { + path = __filename + + prepare(node: IronfishNode): IDatabase { + return node.wallet.walletDb.db + } + + async forward( + node: IronfishNode, + _db: IDatabase, + tx: IDatabaseTransaction | undefined, + logger: Logger, + ): Promise { + const chainDb = createDB({ location: node.config.chainDatabasePath }) + await chainDb.open() + + const chainAssets: IDatabaseStore = chainDb.addStore({ + name: 'bA', + keyEncoding: BUFFER_ENCODING, + valueEncoding: new AssetValueEncoding(), + }) + + const accounts = [] + for await (const accountValue of node.wallet.walletDb.loadAccounts(tx)) { + accounts.push( + new Account({ + ...accountValue, + walletDb: node.wallet.walletDb, + }), + ) + } + + logger.info(`Backfilling assets for ${accounts.length} accounts`) + + for (const account of accounts) { + logger.info('') + logger.info(` Backfilling assets for account ${account.name}`) + + for await (const { note, sequence, blockHash: hash } of account.getNotes()) { + const asset = await node.wallet.walletDb.getAsset(account, note.assetId(), tx) + + if (!asset) { + const chainAsset = await chainAssets.get(note.assetId()) + Assert.isNotUndefined(chainAsset, 'Asset must be non-null in the chain') + + logger.info(` Backfilling ${BufferUtils.toHuman(chainAsset.name)} from chain`) + await account.saveAssetFromChain( + chainAsset.createdTransactionHash, + chainAsset.id, + chainAsset.metadata, + chainAsset.name, + chainAsset.owner, + { hash, sequence }, + tx, + ) + } + } + + logger.info(` Completed backfilling assets for account ${account.name}`) + } + + await chainDb.close() + logger.info('') + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + async backward(): Promise {} +} diff --git a/ironfish/src/migrations/data/index.ts b/ironfish/src/migrations/data/index.ts index 290a43b486..9d0df00476 100644 --- a/ironfish/src/migrations/data/index.ts +++ b/ironfish/src/migrations/data/index.ts @@ -6,5 +6,14 @@ import { Migration014 } from './014-blockchain' import { Migration015 } from './015-wallet' import { Migration016 } from './016-sequence-to-tx' import { Migration017 } from './017-sequence-encoding' +import { Migration018 } from './018-backfill-wallet-assets' +import { Migration019 } from './019-backfill-wallet-assets-from-chain' -export const MIGRATIONS = [Migration014, Migration015, Migration016, Migration017] +export const MIGRATIONS = [ + Migration014, + Migration015, + Migration016, + Migration017, + Migration018, + Migration019, +] diff --git a/ironfish/src/mining/index.ts b/ironfish/src/mining/index.ts index 6b338677ab..dfa2aa71ff 100644 --- a/ironfish/src/mining/index.ts +++ b/ironfish/src/mining/index.ts @@ -9,5 +9,5 @@ export { WebhookNotifier } from './webhooks' export { MiningPool } from './pool' export { MiningPoolMiner } from './poolMiner' export { MiningSoloMiner } from './soloMiner' -export { StratumClient } from './stratum' +export { StratumClient, StratumTcpClient, StratumTlsClient } from './stratum' export { MiningStatusMessage } from './stratum' diff --git a/ironfish/src/mining/pool.ts b/ironfish/src/mining/pool.ts index b464bccd99..5118706db5 100644 --- a/ironfish/src/mining/pool.ts +++ b/ironfish/src/mining/pool.ts @@ -3,17 +3,21 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { blake3 } from '@napi-rs/blake-hash' import LeastRecentlyUsed from 'blru' +import tls from 'tls' import { Assert } from '../assert' import { Config } from '../fileStores/config' import { Logger } from '../logger' import { Target } from '../primitives/target' +import { Transaction } from '../primitives/transaction' import { RpcSocketClient } from '../rpc/clients' import { SerializedBlockTemplate } from '../serde/BlockTemplateSerde' import { BigIntUtils } from '../utils/bigint' import { ErrorUtils } from '../utils/error' import { FileUtils } from '../utils/file' import { SetIntervalToken, SetTimeoutToken } from '../utils/types' +import { TransactionStatus } from '../wallet' import { MiningPoolShares } from './poolShares' +import { StratumTcpAdapter, StratumTlsAdapter } from './stratum/adapters' import { MiningStatusMessage } from './stratum/messages' import { StratumServer } from './stratum/stratumServer' import { StratumServerClient } from './stratum/stratumServerClient' @@ -40,9 +44,6 @@ export class MiningPool { private eventLoopTimeout: SetTimeoutToken | null - private attemptPayoutInterval: number - private nextPayoutAttempt: number - name: string nextMiningRequestId: number @@ -64,8 +65,6 @@ export class MiningPool { config: Config logger: Logger webhooks?: WebhookNotifier[] - host?: string - port?: number banning?: boolean }) { this.rpc = options.rpc @@ -75,8 +74,6 @@ export class MiningPool { pool: this, config: options.config, logger: this.logger, - host: options.host, - port: options.port, banning: options.banning, }) this.config = options.config @@ -99,9 +96,6 @@ export class MiningPool { this.eventLoopTimeout = null - this.attemptPayoutInterval = this.config.get('poolAttemptPayoutInterval') - this.nextPayoutAttempt = new Date().getTime() - this.recalculateTargetInterval = null this.notifyStatusInterval = null } @@ -112,10 +106,11 @@ export class MiningPool { logger: Logger webhooks?: WebhookNotifier[] enablePayouts?: boolean - host?: string - port?: number - balancePercentPayoutFlag?: number + host: string + port: number banning?: boolean + tls?: boolean + tlsOptions?: tls.TlsOptions }): Promise { const shares = await MiningPoolShares.init({ rpc: options.rpc, @@ -123,19 +118,38 @@ export class MiningPool { logger: options.logger, webhooks: options.webhooks, enablePayouts: options.enablePayouts, - balancePercentPayoutFlag: options.balancePercentPayoutFlag, }) - return new MiningPool({ + const pool = new MiningPool({ rpc: options.rpc, logger: options.logger, config: options.config, webhooks: options.webhooks, - host: options.host, - port: options.port, shares, banning: options.banning, }) + + if (options.tls) { + Assert.isNotUndefined(options.tlsOptions) + pool.stratum.mount( + new StratumTlsAdapter({ + logger: options.logger, + host: options.host, + port: options.port, + tlsOptions: options.tlsOptions, + }), + ) + } else { + pool.stratum.mount( + new StratumTcpAdapter({ + logger: options.logger, + host: options.host, + port: options.port, + }), + ) + } + + return pool } async start(): Promise { @@ -147,12 +161,8 @@ export class MiningPool { this.started = true await this.shares.start() - this.logger.info( - `Starting stratum server v${String(this.stratum.version)} on ${this.stratum.host}:${ - this.stratum.port - }`, - ) - this.stratum.start() + this.logger.info(`Starting stratum server v${String(this.stratum.version)}`) + await this.stratum.start() this.logger.info('Connecting to node...') this.rpc.onClose.on(this.onDisconnectRpc) @@ -179,7 +189,7 @@ export class MiningPool { this.started = false this.rpc.onClose.off(this.onDisconnectRpc) this.rpc.close() - this.stratum.stop() + await this.stratum.stop() await this.shares.stop() @@ -211,10 +221,10 @@ export class MiningPool { const eventLoopStartTime = new Date().getTime() - if (this.nextPayoutAttempt <= eventLoopStartTime) { - this.nextPayoutAttempt = new Date().getTime() + this.attemptPayoutInterval * 1000 - await this.shares.createPayout() - } + await this.shares.rolloverPayoutPeriod() + await this.updateUnconfirmedBlocks() + await this.updateUnconfirmedPayoutTransactions() + await this.shares.createNewPayout() const eventLoopEndTime = new Date().getTime() const eventLoopDuration = eventLoopEndTime - eventLoopStartTime @@ -290,6 +300,12 @@ export class MiningPool { const hashRate = await this.estimateHashRate() const hashedHeaderHex = hashedHeader.toString('hex') + const minersFee = new Transaction( + Buffer.from(blockTemplate.transactions[0], 'hex'), + ).fee() + + await this.shares.submitBlock(blockTemplate.header.sequence, hashedHeaderHex, minersFee) + this.logger.info( `Block ${hashedHeaderHex} submitted successfully! ${FileUtils.formatHashRate( hashRate, @@ -517,4 +533,39 @@ export class MiningPool { return status } + + async updateUnconfirmedBlocks(): Promise { + const unconfirmedBlocks = await this.shares.unconfirmedBlocks() + + for (const block of unconfirmedBlocks) { + const blockInfoResp = await this.rpc.getBlockInfo({ + hash: block.blockHash, + confirmations: this.config.get('confirmations'), + }) + + const { main, confirmed } = blockInfoResp.content.metadata + await this.shares.updateBlockStatus(block, main, confirmed) + } + } + + async updateUnconfirmedPayoutTransactions(): Promise { + const unconfirmedTransactions = await this.shares.unconfirmedPayoutTransactions() + + for (const transaction of unconfirmedTransactions) { + const transactionInfoResp = await this.rpc.getAccountTransaction({ + hash: transaction.transactionHash, + confirmations: this.config.get('confirmations'), + }) + + const transactionInfo = transactionInfoResp.content.transaction + if (!transactionInfo) { + this.logger.debug(`Transaction ${transaction.transactionHash} not found.`) + continue + } + + const confirmed = transactionInfo.status === TransactionStatus.CONFIRMED + const expired = transactionInfo.status === TransactionStatus.EXPIRED + await this.shares.updatePayoutTransactionStatus(transaction, confirmed, expired) + } + } } diff --git a/ironfish/src/mining/poolDatabase/database.test.ts b/ironfish/src/mining/poolDatabase/database.test.ts new file mode 100644 index 0000000000..dcabf3bd93 --- /dev/null +++ b/ironfish/src/mining/poolDatabase/database.test.ts @@ -0,0 +1,581 @@ +/* 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/. */ + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { LogLevel } from 'consola' +import { Assert } from '../../assert' +import { Config } from '../../fileStores' +import { NodeFileProvider } from '../../fileSystems' +import { createRootLogger } from '../../logger' +import { getUniqueTestDataDir } from '../../testUtilities/utils' +import { PoolDatabase, RawDatabaseBlock, RawDatabasePayoutTransaction } from './database' + +describe('poolDatabase', () => { + let db: PoolDatabase + + beforeEach(async () => { + const logger = createRootLogger().withTag('test') + logger.level = LogLevel.Silent + const dataDir = getUniqueTestDataDir() + const fileSystem = new NodeFileProvider() + await fileSystem.init() + // TODO(mat): It would be convenient if we didn't need a filesystem for Config for tests + const config = new Config(fileSystem, dataDir) + + db = await PoolDatabase.init({ + config, + logger, + dbPath: ':memory:', + }) + + await db.start() + }) + + afterEach(async () => { + await db.stop() + }) + + it('payout periods', async () => { + const payoutPeriod0 = await db.getCurrentPayoutPeriod() + expect(payoutPeriod0).toBeUndefined() + + const now = new Date().getTime() + await db.rolloverPayoutPeriod(now) + + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1, 'payoutPeriod1 should exist') + expect(payoutPeriod1.start).toEqual(now) + + const nextTimestamp = now + 10 + await db.rolloverPayoutPeriod(nextTimestamp) + + const payoutPeriod2 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2, 'payoutPeriod2 should exist') + expect(payoutPeriod2.start).toEqual(nextTimestamp) + + const period1Raw = await db['db'].get( + 'SELECT * FROM payoutPeriod WHERE id = ?', + payoutPeriod1.id, + ) + Assert.isNotUndefined(period1Raw, 'period1Raw should exist') + expect(period1Raw.end).toEqual(payoutPeriod2.start - 1) + }) + + it('blocks', async () => { + const getBlock = async (id: number): Promise => { + const result = await db['db'].get( + 'SELECT * FROM block WHERE id = ?', + id, + ) + Assert.isNotUndefined(result) + return result + } + + const minerReward = '2000560003' + + await db.rolloverPayoutPeriod(new Date().getTime()) + + // Block 1: main chain and confirmed + const block1Id = await db.newBlock(1, 'hash1', minerReward) + Assert.isNotUndefined(block1Id) + await db.updateBlockStatus(block1Id, true, true) + + await expect(getBlock(block1Id)).resolves.toMatchObject({ + id: block1Id, + main: 1, + confirmed: 1, + minerReward, + }) + + // Block 2: forked and confirmed + const block2Id = await db.newBlock(1, 'hash2', minerReward) + Assert.isNotUndefined(block2Id) + await db.updateBlockStatus(block2Id, false, true) + + await expect(getBlock(block2Id)).resolves.toMatchObject({ + id: block2Id, + main: 0, + confirmed: 1, + minerReward, + }) + + // Block 3: main chain and unconfirmed + const block3Id = await db.newBlock(2, 'hash3', minerReward) + Assert.isNotUndefined(block3Id) + await db.updateBlockStatus(block3Id, true, false) + + await expect(getBlock(block3Id)).resolves.toMatchObject({ + id: block3Id, + main: 1, + confirmed: 0, + minerReward, + }) + + // Block 4: forked and unconfirmed + const block4Id = await db.newBlock(2, 'hash4', minerReward) + Assert.isNotUndefined(block4Id) + await db.updateBlockStatus(block4Id, false, false) + + await expect(getBlock(block4Id)).resolves.toMatchObject({ + id: block4Id, + main: 0, + confirmed: 0, + minerReward, + }) + + const blocks = await db.unconfirmedBlocks() + expect(blocks.length).toEqual(2) + expect(blocks[0].id).toEqual(block3Id) + expect(blocks[1].id).toEqual(block4Id) + }) + + it('transactions', async () => { + const getTransaction = async (id: number): Promise => { + const result = await db['db'].get( + 'SELECT * FROM payoutTransaction WHERE id = ?', + id, + ) + Assert.isNotUndefined(result) + return result + } + + await db.rolloverPayoutPeriod(new Date().getTime()) + + const payoutPeriod = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod) + + // Transaction 1: confirmed, unexpired + const hash1 = 'hash1' + const transaction1Id = await db.newTransaction(hash1, payoutPeriod.id) + Assert.isNotUndefined(transaction1Id) + await db.updateTransactionStatus(transaction1Id, true, false) + + await expect(getTransaction(transaction1Id)).resolves.toMatchObject({ + payoutPeriodId: payoutPeriod.id, + id: transaction1Id, + transactionHash: hash1, + confirmed: 1, + expired: 0, + }) + + // Transaction 2: unconfirmed, expired + const hash2 = 'hash2' + const transaction2Id = await db.newTransaction(hash2, payoutPeriod.id) + Assert.isNotUndefined(transaction2Id) + await db.updateTransactionStatus(transaction2Id, false, true) + + await expect(getTransaction(transaction2Id)).resolves.toMatchObject({ + payoutPeriodId: payoutPeriod.id, + id: transaction2Id, + transactionHash: hash2, + confirmed: 0, + expired: 1, + }) + + // Transaction 3: unconfirmed, unexpired + const hash3 = 'hash3' + const transaction3Id = await db.newTransaction(hash3, payoutPeriod.id) + Assert.isNotUndefined(transaction3Id) + await db.updateTransactionStatus(transaction3Id, false, false) + + await expect(getTransaction(transaction3Id)).resolves.toMatchObject({ + payoutPeriodId: payoutPeriod.id, + id: transaction3Id, + transactionHash: hash3, + confirmed: 0, + expired: 0, + }) + + const transactions = await db.unconfirmedTransactions() + expect(transactions.length).toEqual(1) + expect(transactions[0].id).toEqual(transaction3Id) + }) + + describe('shares', () => { + beforeEach(async () => { + await db.rolloverPayoutPeriod(new Date().getTime()) + }) + + const getShares = () => { + return db['db'].all('SELECT * FROM payoutShare') + } + + it('inserts new shares', async () => { + const address1 = 'testPublicAddress1' + const address2 = 'testPublicAddress2' + + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + await db.newShare(address1) + await db.newShare(address1) + + await db.rolloverPayoutPeriod(new Date().getTime() + 1_000_000) + const payoutPeriod2 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2) + + await db.newShare(address2) + + const shares = await getShares() + expect(shares.length).toEqual(3) + expect(shares[0]).toMatchObject({ + publicAddress: address1, + payoutTransactionId: null, + payoutPeriodId: payoutPeriod1.id, + }) + expect(shares[1]).toMatchObject({ + publicAddress: address1, + payoutTransactionId: null, + payoutPeriodId: payoutPeriod1.id, + }) + expect(shares[2]).toMatchObject({ + publicAddress: address2, + payoutTransactionId: null, + payoutPeriodId: payoutPeriod2.id, + }) + }) + + it('shareCountSince', async () => { + const address1 = 'publicAddress1' + const address2 = 'publicAddress2' + + const before = new Date().getTime() - 10 * 1000 // 10 seconds in the past + + await db.newShare(address1) + await db.newShare(address2) + + const after = new Date().getTime() + 10 * 1000 // 10 seconds in the future + + await expect(db.shareCountSince(before)).resolves.toEqual(2) + await expect(db.shareCountSince(after)).resolves.toEqual(0) + + await expect(db.shareCountSince(before, address1)).resolves.toEqual(1) + await expect(db.shareCountSince(after, address1)).resolves.toEqual(0) + }) + + it('marks shares paid', async () => { + const address = 'testPublicAddress' + + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + await db.newShare(address) + + await db.rolloverPayoutPeriod(new Date().getTime() + 1_000_000) + + const payoutPeriod2 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2) + + await db.newShare(address) + + const transactionId = await db.newTransaction('txHash1', payoutPeriod1.id) + Assert.isNotUndefined(transactionId) + + await db.markSharesPaid(payoutPeriod1.id, transactionId, [address]) + + const shares = await getShares() + expect(shares.length).toEqual(2) + expect(shares[0]).toMatchObject({ + payoutPeriodId: payoutPeriod1.id, + payoutTransactionId: transactionId, + }) + expect(shares[1]).toMatchObject({ + payoutPeriodId: payoutPeriod2.id, + payoutTransactionId: null, + }) + }) + + it('marks shares unpaid', async () => { + const address = 'testPublicAddress' + + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + await db.newShare(address) + + const transactionId = await db.newTransaction('txHash1', payoutPeriod1.id) + Assert.isNotUndefined(transactionId) + + await db.markSharesPaid(payoutPeriod1.id, transactionId, [address]) + + const paidShares = await getShares() + expect(paidShares.length).toEqual(1) + expect(paidShares[0].payoutTransactionId).toEqual(transactionId) + + await db.markSharesUnpaid(transactionId) + + const unpaidShares = await getShares() + expect(unpaidShares.length).toEqual(1) + expect(unpaidShares[0].payoutTransactionId).toBeNull() + }) + + it('deletes unpayable shares', async () => { + const payoutPeriod = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod) + + await db.newShare('publicAddress1') + + // Sanity check + await expect(db.payoutPeriodShareCount(payoutPeriod.id)).resolves.toEqual(1) + + await db.deleteUnpayableShares(payoutPeriod.id) + + await expect(db.payoutPeriodShareCount(payoutPeriod.id)).resolves.toEqual(0) + }) + + it('payoutAddresses', async () => { + const address1 = 'testPublicAddress1' + const address2 = 'testPublicAddress2' + const address3 = 'testPublicAddress3' + + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + // Address 1: 2 shares + await db.newShare(address1) + await db.newShare(address1) + // Address 2: 3 shares + await db.newShare(address2) + await db.newShare(address2) + await db.newShare(address2) + // Address 3: 0 shares + + await db.rolloverPayoutPeriod(new Date().getTime() + 100) + + await db.newShare(address1) + await db.newShare(address2) + await db.newShare(address3) + + const addresses = await db.payoutAddresses(payoutPeriod1.id) + expect(addresses.length).toEqual(2) + expect(addresses[0]).toMatchObject({ + publicAddress: address1, + shareCount: 2, + }) + expect(addresses[1]).toMatchObject({ + publicAddress: address2, + shareCount: 3, + }) + }) + + it('earliestOutstandingPayoutPeriod', async () => { + const address = 'testPublicAddress' + + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + await db.newShare(address) + + await db.rolloverPayoutPeriod(new Date().getTime() + 100) + + await db.newShare(address) + + const payoutPeriod2 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2) + + await db.rolloverPayoutPeriod(new Date().getTime() + 200) + + const earliest1 = await db.earliestOutstandingPayoutPeriod() + Assert.isNotUndefined(earliest1) + expect(earliest1.id).toEqual(payoutPeriod1.id) + + await db.markSharesPaid(payoutPeriod1.id, 1, [address]) + + const earliest2 = await db.earliestOutstandingPayoutPeriod() + Assert.isNotUndefined(earliest2) + expect(earliest2.id).toEqual(payoutPeriod2.id) + }) + + it('payoutPeriodShareCount', async () => { + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + const shareCount1 = await db.payoutPeriodShareCount(payoutPeriod1.id) + expect(shareCount1).toEqual(0) + + await db.newShare('addr1') + await db.newShare('addr1') + await db.newShare('addr2') + + const shareCount2 = await db.payoutPeriodShareCount(payoutPeriod1.id) + expect(shareCount2).toEqual(3) + + await db.rolloverPayoutPeriod(new Date().getTime() + 100) + + // Shares goes into other payout period, count should be unchanged + await db.newShare('addr1') + await db.newShare('addr3') + + const shareCount3 = await db.payoutPeriodShareCount(payoutPeriod1.id) + expect(shareCount3).toEqual(3) + }) + + it('pendingShareCount', async () => { + const payoutPeriod = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod) + + const publicAddress1 = 'publicAddress1' + const publicAddress2 = 'publicAddress2' + + // 1 share each that is paid out + await db.newShare(publicAddress1) + await db.newShare(publicAddress2) + + await db.markSharesPaid(payoutPeriod.id, 1, [publicAddress1, publicAddress2]) + + // 1 share each that is not paid out + await db.newShare(publicAddress1) + await db.newShare(publicAddress2) + + await db.rolloverPayoutPeriod(new Date().getTime() + 100) + + // 1 share each that is not paid out in another payout period + await db.newShare(publicAddress1) + await db.newShare(publicAddress2) + + await expect(db.pendingShareCount()).resolves.toEqual(4) + await expect(db.pendingShareCount(publicAddress1)).resolves.toEqual(2) + }) + + it('getPayoutReward', async () => { + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + // Sanity check + await expect(db.getPayoutReward(payoutPeriod1.id)).resolves.toEqual(0n) + + // Period 1 + const block1Id = await db.newBlock(1, 'hash1', '100') + Assert.isNotUndefined(block1Id) + const block2Id = await db.newBlock(1, 'hash2', '100') + Assert.isNotUndefined(block2Id) + + await db.updateBlockStatus(block1Id, true, true) + await db.updateBlockStatus(block2Id, false, true) + + // Period 1 reward: 50% of period 1. No previous periods to accumulate value + await expect(db.getPayoutReward(payoutPeriod1.id)).resolves.toEqual(50n) + + // Period 2 + await db.rolloverPayoutPeriod(new Date().getTime() + 100) + const payoutPeriod2 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2) + + const block3Id = await db.newBlock(3, 'hash3', '50') + Assert.isNotUndefined(block3Id) + const block4Id = await db.newBlock(4, 'hash4', '50') + Assert.isNotUndefined(block4Id) + + await db.updateBlockStatus(block3Id, true, true) + await db.updateBlockStatus(block4Id, true, true) + + // Period 2 reward: 50% of period 2 + 25% of period 1 + await expect(db.getPayoutReward(payoutPeriod2.id)).resolves.toEqual(75n) + + // Period 3 + await db.rolloverPayoutPeriod(new Date().getTime() + 200) + const payoutPeriod3 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod3) + + const block5Id = await db.newBlock(5, 'hash5', '100') + Assert.isNotUndefined(block5Id) + + await db.updateBlockStatus(block5Id, true, true) + + // Period 3 reward: 50% of period 3 + 25% of period 2 + 15% of period 1 + await expect(db.getPayoutReward(payoutPeriod3.id)).resolves.toEqual(90n) + + // Period 4 + await db.rolloverPayoutPeriod(new Date().getTime() + 300) + const payoutPeriod4 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod4) + + const block6Id = await db.newBlock(6, 'hash6', '100') + Assert.isNotUndefined(block6Id) + + await db.updateBlockStatus(block6Id, true, true) + + // Period 4 reward: 50% of period 4 + 25% of period 3 + 15% of period 2 + 10% of period 1 + await expect(db.getPayoutReward(payoutPeriod4.id)).resolves.toEqual(100n) + + // Period 5 - sanity check that period 1 is not included + await db.rolloverPayoutPeriod(new Date().getTime() + 400) + const payoutPeriod5 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod5) + + const block7Id = await db.newBlock(7, 'hash7', '100') + Assert.isNotUndefined(block7Id) + + await db.updateBlockStatus(block7Id, true, true) + + // Period 5 reward: 50% of period 5 + 25% of period 4 + 15% of period 3 + 10% of period 2 + 0% of period 1 + await expect(db.getPayoutReward(payoutPeriod5.id)).resolves.toEqual(100n) + }) + + it('payoutPeriodBlocksConfirmed', async () => { + const payoutPeriod1 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + // Sanity check + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod1.id)).resolves.toEqual(true) + + // Period 1 + const block1Id = await db.newBlock(1, 'hash1', '100') + Assert.isNotUndefined(block1Id) + const block2Id = await db.newBlock(1, 'hash3', '100') + Assert.isNotUndefined(block2Id) + + await db.updateBlockStatus(block1Id, true, true) + await db.updateBlockStatus(block2Id, false, true) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod1.id)).resolves.toEqual(true) + + await db.updateBlockStatus(block2Id, true, false) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod1.id)).resolves.toEqual(false) + + // Period 2 + await db.rolloverPayoutPeriod(new Date().getTime() + 100) + const payoutPeriod2 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2) + + const block3Id = await db.newBlock(3, 'hash3', '100') + Assert.isNotUndefined(block3Id) + await db.updateBlockStatus(block3Id, true, true) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod2.id)).resolves.toEqual(false) + + await db.updateBlockStatus(block2Id, false, true) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod2.id)).resolves.toEqual(true) + + // Period 3 - no blocks + await db.rolloverPayoutPeriod(new Date().getTime() + 200) + const payoutPeriod3 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod3) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod3.id)).resolves.toEqual(true) + + // Period 4 + await db.rolloverPayoutPeriod(new Date().getTime() + 300) + const payoutPeriod4 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod4) + + const block4Id = await db.newBlock(4, 'hash4', '100') + Assert.isNotUndefined(block4Id) + await db.updateBlockStatus(block4Id, true, true) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod4.id)).resolves.toEqual(true) + + // Period 5 - does not include blocks from period 1 + await db.rolloverPayoutPeriod(new Date().getTime() + 400) + const payoutPeriod5 = await db.getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod5) + + await db.updateBlockStatus(block1Id, true, false) + + await expect(db.payoutPeriodBlocksConfirmed(payoutPeriod5.id)).resolves.toEqual(true) + }) + }) +}) diff --git a/ironfish/src/mining/poolDatabase/database.ts b/ironfish/src/mining/poolDatabase/database.ts index ba142e4849..4214c4f39a 100644 --- a/ironfish/src/mining/poolDatabase/database.ts +++ b/ironfish/src/mining/poolDatabase/database.ts @@ -3,27 +3,29 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Database, open } from 'sqlite' import sqlite3 from 'sqlite3' +import { Assert } from '../../assert' import { Config } from '../../fileStores/config' import { NodeFileProvider } from '../../fileSystems/nodeFileSystem' import { Logger } from '../../logger' import { Migrator } from './migrator' +const PREVIOUS_PAYOUT_PERIODS = 3 +const MAX_ADDRESSES_PER_PAYOUT = 250 + export class PoolDatabase { private readonly db: Database - private readonly config: Config private readonly migrations: Migrator - private readonly attemptPayoutInterval: number - private readonly successfulPayoutInterval: number constructor(options: { db: Database; config: Config; logger: Logger }) { this.db = options.db - this.config = options.config this.migrations = new Migrator({ db: options.db, logger: options.logger }) - this.attemptPayoutInterval = this.config.get('poolAttemptPayoutInterval') - this.successfulPayoutInterval = this.config.get('poolSuccessfulPayoutInterval') } - static async init(options: { config: Config; logger: Logger }): Promise { + static async init(options: { + config: Config + logger: Logger + dbPath?: string + }): Promise { const fs = new NodeFileProvider() await fs.init() @@ -31,7 +33,7 @@ export class PoolDatabase { await fs.mkdir(poolFolder, { recursive: true }) const db = await open({ - filename: fs.join(poolFolder, '/database.sqlite'), + filename: options.dbPath || fs.join(poolFolder, '/database.sqlite'), driver: sqlite3.Database, }) @@ -51,24 +53,28 @@ export class PoolDatabase { } async newShare(publicAddress: string): Promise { - await this.db.run('INSERT INTO share (publicAddress) VALUES (?)', publicAddress) + const sql = ` + INSERT INTO payoutShare (payoutPeriodId, publicAddress) + VALUES ( + (SELECT id FROM payoutPeriod WHERE end IS NULL), + ? + ) + ` + await this.db.run(sql, publicAddress) } - async getSharesForPayout(timestamp: number): Promise { - return await this.db.all( - "SELECT * FROM share WHERE payoutId IS NULL AND createdAt < datetime(?, 'unixepoch')", - timestamp, - ) - } + async shareCountSince(timestamp: number, publicAddress?: string): Promise { + // JS timestamps have millisecond resolution, sqlite timestamps have second resolution + const sqlTimestamp = Math.floor(timestamp / 1000) - async getSharesCountForPayout(publicAddress?: string): Promise { - let sql = 'SELECT COUNT(*) AS count from share WHERE payoutId IS NULL' + let sql = + "SELECT COUNT(id) AS count FROM payoutShare WHERE createdAt > datetime(?, 'unixepoch')" if (publicAddress) { sql += ' AND publicAddress = ?' } - const result = await this.db.get<{ count: number }>(sql, publicAddress) + const result = await this.db.get<{ count: number }>(sql, sqlTimestamp, publicAddress) if (result === undefined) { return 0 } @@ -76,64 +82,318 @@ export class PoolDatabase { return result.count } - async newPayout(timestamp: number): Promise { - // Create a payout row if the most recent successful payout was greater than the payout interval - // and the most recent payout was greater than the attempt interval, in case of failed or long - // running payouts. - const successfulPayoutCutoff = timestamp - this.successfulPayoutInterval - const attemptPayoutCutoff = timestamp - this.attemptPayoutInterval + async getCurrentPayoutPeriod(): Promise { + return await this.db.get( + 'SELECT * FROM payoutPeriod WHERE end is null', + ) + } + + async rolloverPayoutPeriod(timestamp: number): Promise { + await this.db.run('BEGIN') + await this.db.run('UPDATE payoutPeriod SET end = ? WHERE end IS NULL', timestamp - 1) + await this.db.run('INSERT INTO payoutPeriod (start) VALUES (?)', timestamp) + await this.db.run('COMMIT') + } + + async newBlock(sequence: number, hash: string, reward: string): Promise { + const sql = ` + INSERT INTO block (payoutPeriodId, blockSequence, blockHash, minerReward) + VALUES ( + (SELECT id FROM payoutPeriod WHERE end IS NULL), + ?, ?, ? + ) + ` - const query = ` - INSERT INTO payout (succeeded) - SELECT FALSE WHERE - NOT EXISTS (SELECT * FROM payout WHERE createdAt > datetime(?, 'unixepoch') AND succeeded = TRUE) - AND NOT EXISTS (SELECT * FROM payout WHERE createdAt > datetime(?, 'unixepoch')) - ` + const result = await this.db.run(sql, sequence, hash, reward) + return result.lastID + } - const result = await this.db.run(query, successfulPayoutCutoff, attemptPayoutCutoff) - if (result.changes !== 0 && result.lastID != null) { - return result.lastID + async unconfirmedBlocks(): Promise { + const rows = await this.db.all( + 'SELECT * FROM block WHERE confirmed = FALSE', + ) + + const results: DatabaseBlock[] = [] + for (const row of rows) { + results.push(parseDatabaseBlock(row)) } - return null + return results + } + + async updateBlockStatus(blockId: number, main: boolean, confirmed: boolean): Promise { + await this.db.run( + 'UPDATE block SET main = ?, confirmed = ? WHERE id = ?', + main, + confirmed, + blockId, + ) } - async markPayoutSuccess( - id: number, - timestamp: number, - transactionHash: string, + async newTransaction(hash: string, payoutPeriodId: number): Promise { + const result = await this.db.run( + 'INSERT INTO payoutTransaction (transactionHash, payoutPeriodId) VALUES (?, ?)', + hash, + payoutPeriodId, + ) + + return result.lastID + } + + async unconfirmedTransactions(): Promise { + const rows = await this.db.all( + 'SELECT * FROM payoutTransaction WHERE confirmed = FALSE AND expired = FALSE', + ) + + const result: DatabasePayoutTransaction[] = [] + for (const row of rows) { + result.push(parseDatabasePayoutTransaction(row)) + } + + return result + } + + async updateTransactionStatus( + transactionId: number, + confirmed: boolean, + expired: boolean, ): Promise { await this.db.run( - 'UPDATE payout SET succeeded = TRUE, transactionHash = ? WHERE id = ?', - id, - transactionHash, + 'UPDATE payoutTransaction SET confirmed = ?, expired = ? WHERE id = ?', + confirmed, + expired, + transactionId, + ) + } + + // Returns a capped number of unique public addresses and the amount of shares + // they earned for a specific payout period + async payoutAddresses( + payoutPeriodId: number, + ): Promise<{ publicAddress: string; shareCount: number }[]> { + const sql = ` + SELECT publicAddress, COUNT(id) shareCount + FROM payoutShare + WHERE + payoutPeriodId = ? + AND payoutTransactionId IS NULL + GROUP BY publicAddress + LIMIT ? + ` + return await this.db.all<{ publicAddress: string; shareCount: number }[]>( + sql, + payoutPeriodId, + MAX_ADDRESSES_PER_PAYOUT, + ) + } + + async markSharesPaid( + payoutPeriodId: number, + payoutTransactionId: number, + publicAddresses: string[], + ): Promise { + Assert.isGreaterThan( + publicAddresses.length, + 0, + 'markSharesPaid must be called with at least 1 address', ) + + const sql = ` + UPDATE payoutShare + SET payoutTransactionId = ? + WHERE + payoutPeriodId = ? + AND publicAddress IN ('${publicAddresses.join("','")}') + ` + + await this.db.run(sql, payoutTransactionId, payoutPeriodId) + } + + async markSharesUnpaid(transactionId: number): Promise { await this.db.run( - "UPDATE share SET payoutId = ? WHERE payoutId IS NULL AND createdAt < datetime(?, 'unixepoch')", - id, - timestamp, + 'UPDATE payoutShare SET payoutTransactionId = NULL WHERE payoutTransactionId = ?', + transactionId, ) } - async shareCountSince(timestamp: number, publicAddress?: string): Promise { - let sql = "SELECT COUNT(id) AS count FROM share WHERE createdAt > datetime(?, 'unixepoch')" + async deleteUnpayableShares(payoutPeriodId: number): Promise { + await this.db.run('DELETE FROM payoutShare WHERE payoutPeriodId = ?', payoutPeriodId) + } + + async earliestOutstandingPayoutPeriod(): Promise { + const sql = ` + SELECT * FROM payoutPeriod WHERE id = ( + SELECT payoutPeriodId FROM payoutShare WHERE payoutTransactionId IS NULL ORDER BY id LIMIT 1 + ) + ` + return await this.db.get(sql) + } + + async payoutPeriodShareCount(payoutPeriodId: number): Promise { + const result = await this.db.get<{ count: number }>( + 'SELECT COUNT(*) AS count FROM payoutShare WHERE payoutPeriodId = ?', + payoutPeriodId, + ) + if (result === undefined) { + return 0 + } + + return result.count + } + + // Returns the shares that have not been paid out independent of payout period + async pendingShareCount(publicAddress?: string): Promise { + let sql = 'SELECT COUNT(*) AS count FROM payoutShare WHERE payoutTransactionId IS NULL' if (publicAddress) { sql += ' AND publicAddress = ?' } - const result = await this.db.get<{ count: number }>(sql, timestamp, publicAddress) + const result = await this.db.get<{ count: number }>(sql, publicAddress) + if (result === undefined) { return 0 } - return result.count } + + // Returns the total payout reward for a specific payout period + async getPayoutReward(payoutPeriodId: number): Promise { + const sql = ` + SELECT + *, + (SELECT SUM(minerReward) FROM block + WHERE + payoutPeriodId = payoutPeriod.id + AND confirmed = TRUE + AND main = TRUE + ) reward + FROM payoutPeriod + WHERE id BETWEEN ? AND ? + ` + + const results = await this.db.all>( + sql, + payoutPeriodId - PREVIOUS_PAYOUT_PERIODS, + payoutPeriodId, + ) + + const percentAmount = { + [payoutPeriodId]: BigInt(50), // 50% of payout period x + [payoutPeriodId - 1]: BigInt(25), // 25% of payout period x-1 + [payoutPeriodId - 2]: BigInt(15), // 15% of payout period x-2 + [payoutPeriodId - 3]: BigInt(10), // 10% of payout period x-3 + } + + // Safety check in case the associated const is changed + Assert.isEqual( + PREVIOUS_PAYOUT_PERIODS + 1, + Object.keys(percentAmount).length, + 'Payout period percent amount needs to have a value for each period', + ) + + let totalReward = BigInt(0) + for (const result of results) { + const reward = BigInt(result.reward || '0') + const amount = (reward * percentAmount[result.id]) / BigInt(100) + totalReward += amount + } + + return totalReward + } + + // Checks the related payouts (the given payout period and the payouts within + // PREVIOUS_PAYOUT_PERIODS) to see if any of them have unconfirmed blocks + async payoutPeriodBlocksConfirmed(payoutPeriodId: number): Promise { + const sql = ` + SELECT * + FROM block + WHERE + payoutPeriodId BETWEEN ? AND ? + AND confirmed = FALSE + ` + const results = await this.db.all( + sql, + payoutPeriodId - PREVIOUS_PAYOUT_PERIODS, + payoutPeriodId, + ) + + if (results.length > 0) { + return false + } + + return true + } +} + +export type DatabasePayoutPeriod = { + id: number + // TODO(mat): Look into why this creates a string instead of a timestamp like start and end + createdAt: string + start: number + end: number | null +} + +export type DatabaseBlock = { + id: number + createdAt: Date + blockSequence: number + blockHash: string + minerReward: bigint + confirmed: boolean + main: boolean + payoutPeriodId: number +} + +export interface RawDatabaseBlock { + id: number + createdAt: string + blockSequence: number + blockHash: string + minerReward: string + confirmed: number + main: number + payoutPeriodId: number +} + +function parseDatabaseBlock(rawBlock: RawDatabaseBlock): DatabaseBlock { + return { + id: rawBlock.id, + createdAt: new Date(rawBlock.createdAt), + blockSequence: rawBlock.blockSequence, + blockHash: rawBlock.blockHash, + minerReward: BigInt(rawBlock.minerReward), + confirmed: Boolean(rawBlock.confirmed), + main: Boolean(rawBlock.main), + payoutPeriodId: rawBlock.payoutPeriodId, + } } -export type DatabaseShare = { +export type DatabasePayoutTransaction = { id: number - publicAddress: string createdAt: Date - payoutId: number | null + transactionHash: string + confirmed: boolean + expired: boolean + payoutPeriodId: number +} + +export interface RawDatabasePayoutTransaction { + id: number + createdAt: string + transactionHash: string + confirmed: number + expired: number + payoutPeriodId: number +} + +function parseDatabasePayoutTransaction(rawTransaction: RawDatabasePayoutTransaction) { + return { + id: rawTransaction.id, + createdAt: new Date(rawTransaction.createdAt), + transactionHash: rawTransaction.transactionHash, + confirmed: Boolean(rawTransaction.confirmed), + expired: Boolean(rawTransaction.expired), + payoutPeriodId: rawTransaction.payoutPeriodId, + } } diff --git a/ironfish/src/mining/poolDatabase/index.ts b/ironfish/src/mining/poolDatabase/index.ts index adf2e634bc..e27ea0f64e 100644 --- a/ironfish/src/mining/poolDatabase/index.ts +++ b/ironfish/src/mining/poolDatabase/index.ts @@ -2,5 +2,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -export { PoolDatabase, DatabaseShare } from './database' +export { PoolDatabase } from './database' export { Migrator } from './migrator' diff --git a/ironfish/src/mining/poolDatabase/migrations/005-add-payout-period-table.ts b/ironfish/src/mining/poolDatabase/migrations/005-add-payout-period-table.ts new file mode 100644 index 0000000000..40c638d2de --- /dev/null +++ b/ironfish/src/mining/poolDatabase/migrations/005-add-payout-period-table.ts @@ -0,0 +1,25 @@ +/* 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 { Database } from 'sqlite' +import { Migration } from '../migration' + +export default class Migration005 extends Migration { + name = '005-add-payout-period-table' + + async forward(db: Database): Promise { + await db.run(` + CREATE TABLE payoutPeriod ( + id INTEGER PRIMARY KEY, + createdAt INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + start INTEGER NOT NULL, + end INTEGER + ); + `) + } + + async backward(db: Database): Promise { + await db.run('DROP TABLE IF EXISTS payoutPeriod;') + } +} diff --git a/ironfish/src/mining/poolDatabase/migrations/006-add-block-table.ts b/ironfish/src/mining/poolDatabase/migrations/006-add-block-table.ts new file mode 100644 index 0000000000..3e72eee4fe --- /dev/null +++ b/ironfish/src/mining/poolDatabase/migrations/006-add-block-table.ts @@ -0,0 +1,30 @@ +/* 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 { Database } from 'sqlite' +import { Migration } from '../migration' + +export default class Migration006 extends Migration { + name = '006-add-block-table' + + async forward(db: Database): Promise { + await db.run(` + CREATE TABLE block ( + id INTEGER PRIMARY KEY, + createdAt INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + blockSequence INTEGER NOT NULL, + blockHash TEXT NOT NULL, + minerReward TEXT NOT NULL, + confirmed BOOLEAN DEFAULT FALSE, + main BOOLEAN DEFAULT TRUE, + payoutPeriodId INTEGER NOT NULL, + CONSTRAINT block_fk_payoutPeriodId FOREIGN KEY (payoutPeriodId) REFERENCES payoutPeriod (id) + ); + `) + } + + async backward(db: Database): Promise { + await db.run('DROP TABLE IF EXISTS block;') + } +} diff --git a/ironfish/src/mining/poolDatabase/migrations/007-add-payout-transaction-table.ts b/ironfish/src/mining/poolDatabase/migrations/007-add-payout-transaction-table.ts new file mode 100644 index 0000000000..d5fb0f65c9 --- /dev/null +++ b/ironfish/src/mining/poolDatabase/migrations/007-add-payout-transaction-table.ts @@ -0,0 +1,28 @@ +/* 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 { Database } from 'sqlite' +import { Migration } from '../migration' + +export default class Migration007 extends Migration { + name = '007-add-payout-transaction-table' + + async forward(db: Database): Promise { + await db.run(` + CREATE TABLE payoutTransaction ( + id INTEGER PRIMARY KEY, + createdAt INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + transactionHash TEXT NOT NULL, + confirmed BOOLEAN DEFAULT FALSE, + expired BOOLEAN DEFAULT FALSE, + payoutPeriodId INTEGER NOT NULL, + CONSTRAINT payoutTransaction_fk_payoutPeriodId FOREIGN KEY (payoutPeriodId) REFERENCES payoutPeriod (id) + ); + `) + } + + async backward(db: Database): Promise { + await db.run('DROP TABLE IF EXISTS payoutTransaction;') + } +} diff --git a/ironfish/src/mining/poolDatabase/migrations/008-add-payout-share-table.ts b/ironfish/src/mining/poolDatabase/migrations/008-add-payout-share-table.ts new file mode 100644 index 0000000000..4cda5e7d32 --- /dev/null +++ b/ironfish/src/mining/poolDatabase/migrations/008-add-payout-share-table.ts @@ -0,0 +1,28 @@ +/* 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 { Database } from 'sqlite' +import { Migration } from '../migration' + +export default class Migration008 extends Migration { + name = '008-add-payout-share-table' + + async forward(db: Database): Promise { + await db.run(` + CREATE TABLE payoutShare ( + id INTEGER PRIMARY KEY, + createdAt INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + publicAddress TEXT NOT NULL, + payoutPeriodId INTEGER NOT NULL, + payoutTransactionId INTEGER, + CONSTRAINT payoutShare_fk_payoutPeriodId FOREIGN KEY (payoutPeriodId) REFERENCES payoutPeriod (id) + CONSTRAINT payoutShare_fk_payoutTransactionId FOREIGN KEY (payoutTransactionId) REFERENCES payoutTransaction (id) + ); + `) + } + + async backward(db: Database): Promise { + await db.run('DROP TABLE IF EXISTS payoutShare;') + } +} diff --git a/ironfish/src/mining/poolDatabase/migrations/009-remove-old-tables.ts b/ironfish/src/mining/poolDatabase/migrations/009-remove-old-tables.ts new file mode 100644 index 0000000000..cb9c0a009f --- /dev/null +++ b/ironfish/src/mining/poolDatabase/migrations/009-remove-old-tables.ts @@ -0,0 +1,41 @@ +/* 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 { Database } from 'sqlite' +import { Migration } from '../migration' + +export default class Migration009 extends Migration { + name = '009-remove-old-tables' + + async forward(db: Database): Promise { + await db.run('DROP INDEX IF EXISTS idx_share_public_address;') + await db.run('DROP INDEX IF EXISTS idx_share_created_at;') + await db.run('DROP TABLE IF EXISTS payout;') + await db.run('DROP TABLE IF EXISTS share;') + } + + async backward(db: Database): Promise { + await db.run(` + CREATE TABLE payout ( + id INTEGER PRIMARY KEY, + createdAt INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + succeeded BOOLEAN DEFAULT FALSE, + transactionHash TEXT + ); + `) + + await db.run(` + CREATE TABLE share ( + id INTEGER PRIMARY KEY, + publicAddress TEXT NOT NULL, + createdAt INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP, + payoutId INTEGER, + CONSTRAINT share_fk_payout_id FOREIGN KEY (payoutId) REFERENCES payout (id) + ); + `) + + await db.run(`CREATE INDEX idx_share_created_at ON share (createdAt);`) + await db.run(`CREATE INDEX idx_share_public_address ON share (publicAddress);`) + } +} diff --git a/ironfish/src/mining/poolDatabase/migrations/index.ts b/ironfish/src/mining/poolDatabase/migrations/index.ts index eafcde70a6..a89cbf01d3 100644 --- a/ironfish/src/mining/poolDatabase/migrations/index.ts +++ b/ironfish/src/mining/poolDatabase/migrations/index.ts @@ -5,5 +5,21 @@ import Migration001 from './001-initial' import Migration002 from './002-add-shares-index' import Migration003 from './003-add-transaction-hash' +import Migration004 from './004-add-shares-address-index' +import Migration005 from './005-add-payout-period-table' +import Migration006 from './006-add-block-table' +import Migration007 from './007-add-payout-transaction-table' +import Migration008 from './008-add-payout-share-table' +import Migration009 from './009-remove-old-tables' -export const MIGRATIONS = [Migration001, Migration002, Migration003] +export const MIGRATIONS = [ + Migration001, + Migration002, + Migration003, + Migration004, + Migration005, + Migration006, + Migration007, + Migration008, + Migration009, +] diff --git a/ironfish/src/mining/poolDatabase/migrator.ts b/ironfish/src/mining/poolDatabase/migrator.ts index 5f757e94ea..8875ff35c9 100644 --- a/ironfish/src/mining/poolDatabase/migrator.ts +++ b/ironfish/src/mining/poolDatabase/migrator.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /* eslint-disable no-console */ +import { LogLevel } from 'consola' import { Database } from 'sqlite' import { Logger } from '../../logger' import { Migration } from './migration' @@ -52,17 +53,17 @@ export class Migrator { this.logger.info('Running migrations:') for (const migration of unapplied) { - process.stdout.write(` Applying ${migration.name}...`) + this.write(` Applying ${migration.name}...`) try { await migration.forward(this.db) await this.db.run(`PRAGMA user_version = ${migration.id};`) } catch (e) { - process.stdout.write(` ERROR\n`) + this.write(` ERROR\n`) console.error(e) throw e } - process.stdout.write(` OK\n`) + this.write(` OK\n`) } await this.db.run('COMMIT;') @@ -74,4 +75,10 @@ export class Migrator { throw e } } + + write(output: string): void { + if (this.logger.level >= LogLevel.Info) { + process.stdout.write(output) + } + } } diff --git a/ironfish/src/mining/poolMiner.ts b/ironfish/src/mining/poolMiner.ts index a60cfb6bcf..1e355b6240 100644 --- a/ironfish/src/mining/poolMiner.ts +++ b/ironfish/src/mining/poolMiner.ts @@ -9,7 +9,7 @@ import { FileUtils } from '../utils/file' import { GraffitiUtils } from '../utils/graffiti' import { PromiseUtils } from '../utils/promise' import { isValidPublicAddress } from '../wallet/validator' -import { StratumClient } from './stratum/stratumClient' +import { StratumClient } from './stratum/clients/client' import { MINEABLE_BLOCK_HEADER_GRAFFITI_OFFSET } from './utils' export class MiningPoolMiner { @@ -35,8 +35,7 @@ export class MiningPoolMiner { batchSize: number logger: Logger publicAddress: string - host: string - port: number + stratum: StratumClient name?: string }) { this.logger = options.logger @@ -50,11 +49,7 @@ export class MiningPoolMiner { const threadCount = options.threadCount ?? 1 this.threadPool = new ThreadPoolHandler(threadCount, options.batchSize, false) - this.stratum = new StratumClient({ - host: options.host, - port: options.port, - logger: options.logger, - }) + this.stratum = options.stratum this.stratum.onConnected.on(() => this.stratum.subscribe(this.publicAddress, this.name)) this.stratum.onSubscribed.on((m) => this.setGraffiti(GraffitiUtils.fromString(m.graffiti))) this.stratum.onSetTarget.on((m) => this.setTarget(m.target)) diff --git a/ironfish/src/mining/poolShares.test.ts b/ironfish/src/mining/poolShares.test.ts new file mode 100644 index 0000000000..3fc581a33a --- /dev/null +++ b/ironfish/src/mining/poolShares.test.ts @@ -0,0 +1,270 @@ +/* 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 { Asset } from '@ironfish/rust-nodejs' +import { LogLevel } from 'consola' +import { Assert } from '../assert' +import { createRootLogger } from '../logger' +import { createRouteTest } from '../testUtilities/routeTest' +import { MiningPoolShares } from './poolShares' + +describe('poolShares', () => { + const routeTest = createRouteTest() + let shares: MiningPoolShares + + beforeEach(async () => { + const logger = createRootLogger().withTag('test') + logger.level = LogLevel.Silent + shares = await MiningPoolShares.init({ + rpc: routeTest.client, + config: routeTest.sdk.config, + logger, + enablePayouts: true, + dbPath: ':memory:', + }) + + await shares.start() + }) + + afterEach(async () => { + await shares.stop() + }) + + it('shareRate', async () => { + jest.useFakeTimers({ legacyFakeTimers: false }) + + const now = new Date(2020, 1, 1).getTime() + jest.setSystemTime(now) + + await shares.rolloverPayoutPeriod() + + const publicAddress1 = 'publicAddress1' + const publicAddress2 = 'publicAddress2' + + await shares.submitShare(publicAddress1) + await shares.submitShare(publicAddress2) + + shares['recentShareCutoff'] = 2 + + const shareRate = await shares.shareRate() + const shareRateAddress = await shares.shareRate(publicAddress1) + + expect(shareRate).toEqual(1) + expect(shareRateAddress).toEqual(0.5) + + jest.useRealTimers() + }) + + it('rolloverPayoutPeriod', async () => { + jest.useFakeTimers({ legacyFakeTimers: false }) + + const now = new Date(2020, 1, 1).getTime() + jest.setSystemTime(now) + + const payoutPeriod0 = await shares['db'].getCurrentPayoutPeriod() + expect(payoutPeriod0).toBeUndefined() + + await shares.rolloverPayoutPeriod() + + const payoutPeriod1 = await shares['db'].getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1, 'payoutPeriod1 should exist') + + // No time has elapsed, so it will not rollover + await shares.rolloverPayoutPeriod() + + const payoutPeriod1A = await shares['db'].getCurrentPayoutPeriod() + expect(payoutPeriod1A).toEqual(payoutPeriod1) + + // Move the clock forward the amount of time needed to trigger a new payout rollover + jest.setSystemTime(now + shares.config.get('poolPayoutPeriodDuration') * 1000) + + await shares.rolloverPayoutPeriod() + + const payoutPeriod2 = await shares['db'].getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2, 'payoutPeriod2 should exist') + expect(payoutPeriod2.id).toEqual(payoutPeriod1.id + 1) + + jest.useRealTimers() + }) + + it('blocks', async () => { + await shares.rolloverPayoutPeriod() + + const reward = BigInt(200000) + await shares.submitBlock(1, 'hash1', reward) + await shares.submitBlock(2, 'hash2', reward * BigInt(-1)) + + const unconfirmedBlocks1 = await shares.unconfirmedBlocks() + expect(unconfirmedBlocks1.length).toEqual(2) + + // This should be a no-op + await shares.updateBlockStatus(unconfirmedBlocks1[0], true, false) + + const unconfirmedBlocks2 = await shares.unconfirmedBlocks() + expect(unconfirmedBlocks2.length).toEqual(2) + + // This should update the 'main' boolean, but the block should still show up + await shares.updateBlockStatus(unconfirmedBlocks2[0], false, false) + + const unconfirmedBlocks3 = await shares.unconfirmedBlocks() + expect(unconfirmedBlocks3.length).toEqual(2) + expect(unconfirmedBlocks3[0]).toMatchObject({ + blockSequence: 1, + blockHash: 'hash1', + minerReward: reward, + main: false, + }) + + await shares.updateBlockStatus(unconfirmedBlocks3[0], false, true) + + const unconfirmedBlocks4 = await shares.unconfirmedBlocks() + expect(unconfirmedBlocks4.length).toEqual(1) + expect(unconfirmedBlocks4[0]).toMatchObject({ + blockSequence: 2, + blockHash: 'hash2', + minerReward: reward, + }) + }) + + describe('transactions', () => { + beforeEach(async () => { + await shares.rolloverPayoutPeriod() + }) + + it('expected flow', async () => { + const payoutPeriod = await shares['db'].getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod) + + await shares['db'].newTransaction('hash1', payoutPeriod.id) + await shares['db'].newTransaction('hash2', payoutPeriod.id) + + const unconfirmedTransactions1 = await shares.unconfirmedPayoutTransactions() + expect(unconfirmedTransactions1.length).toEqual(2) + + // This should be a no-op + await shares.updatePayoutTransactionStatus(unconfirmedTransactions1[0], false, false) + + const unconfirmedTransactions2 = await shares.unconfirmedPayoutTransactions() + expect(unconfirmedTransactions2.length).toEqual(2) + + await shares.updatePayoutTransactionStatus(unconfirmedTransactions1[0], true, false) + + const unconfirmedTransactions3 = await shares.unconfirmedPayoutTransactions() + expect(unconfirmedTransactions3.length).toEqual(1) + + await shares.updatePayoutTransactionStatus(unconfirmedTransactions1[1], false, true) + + const unconfirmedTransactions4 = await shares.unconfirmedPayoutTransactions() + expect(unconfirmedTransactions4.length).toEqual(0) + }) + + it('expired transactions should mark shares unpaid', async () => { + const payoutPeriod = await shares['db'].getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod) + + const address = 'testPublicAddress' + + await shares['db'].newShare(address) + const transactionId = await shares['db'].newTransaction('hash1', payoutPeriod.id) + Assert.isNotUndefined(transactionId) + await shares['db'].markSharesPaid(payoutPeriod.id, transactionId, [address]) + + // All shares paid out, so there should be no outstanding payout periods + const outstandingPeriod1 = await shares['db'].earliestOutstandingPayoutPeriod() + expect(outstandingPeriod1).toBeUndefined() + + const unconfirmedTransactions = await shares.unconfirmedPayoutTransactions() + expect(unconfirmedTransactions.length).toEqual(1) + + // Mark transaction expired + await shares.updatePayoutTransactionStatus(unconfirmedTransactions[0], false, true) + + // Share has been marked unpaid, so the payout period should be outstanding again + const outstandingPeriod2 = await shares['db'].earliestOutstandingPayoutPeriod() + expect(outstandingPeriod2).toBeDefined() + }) + }) + + it('createNewPayout', async () => { + jest.useFakeTimers({ legacyFakeTimers: false }) + + const now = new Date(2020, 1, 1).getTime() + jest.setSystemTime(now) + + const publicAddress1 = 'testPublicAddress1' + const publicAddress2 = 'testPublicAddress2' + + await shares.rolloverPayoutPeriod() + + const payoutPeriod1 = await shares['db'].getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod1) + + // Setup some shares to be paid out + await shares.submitShare(publicAddress1) + await shares.submitShare(publicAddress2) + await shares.submitShare(publicAddress2) + + // Setup a block for some reward to pay out + await shares.submitBlock(1, 'blockHash1', BigInt(102)) + const blocks = await shares.unconfirmedBlocks() + expect(blocks.length).toEqual(1) + await shares.updateBlockStatus(blocks[0], true, true) + + // Move the clock forward the amount of time needed to trigger a new payout rollover + jest.setSystemTime(now + shares.config.get('poolPayoutPeriodDuration') * 1000) + + // Setup some shares to not be paid out since they are in a separate period + await shares.rolloverPayoutPeriod() + const payoutPeriod2 = await shares['db'].getCurrentPayoutPeriod() + Assert.isNotUndefined(payoutPeriod2) + + await shares.submitShare(publicAddress1) + await shares.submitShare(publicAddress2) + + const hasBalanceSpy = jest.spyOn(shares, 'hasConfirmedBalance').mockResolvedValueOnce(true) + const sendTransactionSpy = jest + .spyOn(shares, 'sendTransaction') + .mockResolvedValueOnce('testTransactionHash') + + // Create payout + await shares.createNewPayout() + + // The expected reward total breakdown should be as follows: + // - 1 period (to simplify the calculation, we're not including any past periods) + // - 1 block reward of 102 + // - Since this block was found in this period, the total reward amount is 102 * 50% = 51 + // - 2 recipients, so we subtract the naive fee of 1 ORE per recipient to + // calculate the reward per share = 49 + // - 3 shares total, so 48 / 3 = 16 reward per share (truncate decimals because ORE is indivisable) + // - 16 reward per share * 3 shares + fee of 2 = 50 + expect(hasBalanceSpy).toHaveBeenCalledWith(BigInt(50)) + const assetId = Asset.nativeId().toString('hex') + expect(sendTransactionSpy).toHaveBeenCalledWith([ + // Address 1 had 1 share, with 16 reward per share = 16 amount + { + publicAddress: publicAddress1, + amount: '16', + memo: `Iron Fish Pool payout ${payoutPeriod1.id}`, + assetId, + }, + // Address 2 had 2 shares, with 16 reward per share = 32 amount + { + publicAddress: publicAddress2, + amount: '32', + memo: `Iron Fish Pool payout ${payoutPeriod1.id}`, + assetId, + }, + ]) + + const transactions = await shares.unconfirmedPayoutTransactions() + expect(transactions.length).toEqual(1) + + await expect(shares['db'].payoutAddresses(payoutPeriod1.id)).resolves.toEqual([]) + + const unpaidShares = await shares['db'].payoutAddresses(payoutPeriod2.id) + expect(unpaidShares.length).toEqual(2) + + jest.useRealTimers() + }) +}) diff --git a/ironfish/src/mining/poolShares.ts b/ironfish/src/mining/poolShares.ts index 5e71e0ae86..384cbed8ce 100644 --- a/ironfish/src/mining/poolShares.ts +++ b/ironfish/src/mining/poolShares.ts @@ -2,17 +2,17 @@ * 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 { Asset } from '@ironfish/rust-nodejs' +import { Assert } from '../assert' import { Config } from '../fileStores/config' import { Logger } from '../logger' -import { RpcSocketClient } from '../rpc/clients/socketClient' -import { ErrorUtils } from '../utils' -import { BigIntUtils } from '../utils/bigint' -import { MapUtils } from '../utils/map' -import { DatabaseShare, PoolDatabase } from './poolDatabase' +import { RpcClient } from '../rpc/clients/client' +import { CurrencyUtils, ErrorUtils } from '../utils' +import { PoolDatabase } from './poolDatabase' +import { DatabaseBlock, DatabasePayoutTransaction } from './poolDatabase/database' import { WebhookNotifier } from './webhooks' export class MiningPoolShares { - readonly rpc: RpcSocketClient + readonly rpc: RpcClient readonly config: Config readonly logger: Logger readonly webhooks: WebhookNotifier[] @@ -23,17 +23,14 @@ export class MiningPoolShares { private poolName: string private recentShareCutoff: number private accountName: string - private balancePercentPayout: bigint - private balancePercentPayoutFlag: number | undefined private constructor(options: { db: PoolDatabase - rpc: RpcSocketClient + rpc: RpcClient config: Config logger: Logger webhooks?: WebhookNotifier[] enablePayouts?: boolean - balancePercentPayoutFlag?: number }) { this.db = options.db this.rpc = options.rpc @@ -45,21 +42,20 @@ export class MiningPoolShares { this.poolName = this.config.get('poolName') this.recentShareCutoff = this.config.get('poolRecentShareCutoff') this.accountName = this.config.get('poolAccountName') - this.balancePercentPayout = BigInt(this.config.get('poolBalancePercentPayout')) - this.balancePercentPayoutFlag = options.balancePercentPayoutFlag } static async init(options: { - rpc: RpcSocketClient + rpc: RpcClient config: Config logger: Logger webhooks?: WebhookNotifier[] enablePayouts?: boolean - balancePercentPayoutFlag?: number + dbPath?: string }): Promise { const db = await PoolDatabase.init({ config: options.config, logger: options.logger, + dbPath: options.dbPath, }) return new MiningPoolShares({ @@ -69,7 +65,6 @@ export class MiningPoolShares { logger: options.logger, webhooks: options.webhooks, enablePayouts: options.enablePayouts, - balancePercentPayoutFlag: options.balancePercentPayoutFlag, }) } @@ -85,97 +80,181 @@ export class MiningPoolShares { await this.db.newShare(publicAddress) } - async createPayout(): Promise { - if (!this.enablePayouts) { + async shareRate(publicAddress?: string): Promise { + const timestamp = new Date().getTime() - this.recentShareCutoff * 1000 + const recentShareCount = await this.db.shareCountSince(timestamp, publicAddress) + + return recentShareCount / this.recentShareCutoff + } + + async sharesPendingPayout(publicAddress?: string): Promise { + return await this.db.pendingShareCount(publicAddress) + } + + async rolloverPayoutPeriod(): Promise { + const payoutPeriodDuration = this.config.get('poolPayoutPeriodDuration') * 1000 + const now = new Date().getTime() + const payoutPeriodCutoff = now - payoutPeriodDuration + + const payoutPeriod = await this.db.getCurrentPayoutPeriod() + + if (payoutPeriod && payoutPeriod.start > payoutPeriodCutoff) { + // Current payout period has not exceeded its duration yet return } - // TODO: Make a max payout amount per transaction - // - its currently possible to have a payout include so many inputs that it expires before it - // gets added to the mempool. suspect this would cause issues elsewhere - // As a simple stop-gap, we could probably make payout interval = every x hours OR if confirmed balance > 200 or something - // OR we could combine them, every x minutes, pay 10 inputs into 1 output? - - // Since timestamps have a 1 second granularity, make the cutoff 1 second ago, just to avoid potential issues - const shareCutoff = new Date() - shareCutoff.setSeconds(shareCutoff.getSeconds() - 1) - const timestamp = Math.floor(shareCutoff.getTime() / 1000) - - // Create a payout in the DB as a form of a lock - const payoutId = await this.db.newPayout(timestamp) - if (payoutId == null) { - this.logger.info( - 'Another payout may be in progress or a payout was made too recently, skipping.', - ) + await this.db.rolloverPayoutPeriod(now) + } + + async submitBlock(sequence: number, hash: string, reward: bigint): Promise { + if (reward < 0) { + reward *= BigInt(-1) + } + + await this.db.newBlock(sequence, hash, reward.toString()) + } + + async unconfirmedBlocks(): Promise { + return await this.db.unconfirmedBlocks() + } + + async updateBlockStatus( + block: DatabaseBlock, + main: boolean, + confirmed: boolean, + ): Promise { + if (main === block.main && confirmed === block.confirmed) { return } - const shares = await this.db.getSharesForPayout(timestamp) - const shareCounts = this.sumShares(shares) + await this.db.updateBlockStatus(block.id, main, confirmed) + } + + async unconfirmedPayoutTransactions(): Promise { + return await this.db.unconfirmedTransactions() + } - if (shareCounts.totalShares === 0) { - this.logger.info('No shares submitted since last payout, skipping.') + async updatePayoutTransactionStatus( + transaction: DatabasePayoutTransaction, + confirmed: boolean, + expired: boolean, + ): Promise { + if (confirmed === transaction.confirmed && expired === transaction.expired) { return } - const balance = await this.rpc.getAccountBalance({ account: this.accountName }) - const confirmedBalance = BigInt(balance.content.confirmed) + await this.db.updateTransactionStatus(transaction.id, confirmed, expired) + + if (expired && !confirmed) { + await this.db.markSharesUnpaid(transaction.id) + } + } + + async createNewPayout(): Promise { + if (!this.enablePayouts) { + return + } + + // Get the earliest payout the has shares that have not yet been paid out + const payoutPeriod = await this.db.earliestOutstandingPayoutPeriod() + if (!payoutPeriod) { + this.logger.debug('No outstanding shares, skipping payout') + return + } - let payoutAmount: number - if (this.balancePercentPayoutFlag !== undefined) { - payoutAmount = BigIntUtils.divide( - confirmedBalance * BigInt(this.balancePercentPayoutFlag), - 100n, + // Ensure all of the blocks submitted during the related periods are + // confirmed so that we are not sending out payouts of incorrect or changing + // amounts + const blocksConfirmed = await this.db.payoutPeriodBlocksConfirmed(payoutPeriod.id) + if (!blocksConfirmed) { + this.logger.debug( + `Payout period ${payoutPeriod.id} has unconfirmed blocks, skipping payout`, ) - } else { - payoutAmount = BigIntUtils.divide(confirmedBalance, this.balancePercentPayout) + return } - if (payoutAmount <= shareCounts.totalShares + shareCounts.shares.size) { - // If the pool cannot pay out at least 1 ORE per share and pay transaction fees, no payout can be made. - this.logger.info('Insufficient funds for payout, skipping.') + // Get the batch of addresses to be paid out and their associated share count + const payoutAddresses = await this.db.payoutAddresses(payoutPeriod.id) + + // Get the total amount earned during the payout (and associated previous payouts) + const totalPayoutReward = await this.db.getPayoutReward(payoutPeriod.id) + + if (totalPayoutReward === 0n) { + // The shares in this period cannot be paid out since no reward exists + await this.db.deleteUnpayableShares(payoutPeriod.id) return } - const transactionReceives = MapUtils.map( - shareCounts.shares, - (shareCount, publicAddress) => { - const payoutPercentage = shareCount / shareCounts.totalShares - const amt = Math.floor(payoutPercentage * payoutAmount) - - return { - publicAddress, - amount: amt.toString(), - memo: `${this.poolName} payout ${shareCutoff.toUTCString()}`, - assetId: Asset.nativeId().toString('hex'), - } - }, + // Subtract the amount of recipients since that's how we estimate a + // transaction fee right now. If we move to use the fee estimator, we will + // need to update this logic as well. + // It is worth noting that this leads to slightly inconsistent payout + // amounts, since 1 payout may have 250 recipients and another may have 5, + // but considering 1 block reward is 2 billion ORE, it is a trivial + // difference. + const feeAmount = BigInt(payoutAddresses.length) + const totalPayoutAmount = totalPayoutReward - feeAmount + + // Get the total amount of shares submitted during the period + const totalShareCount = await this.db.payoutPeriodShareCount(payoutPeriod.id) + + // Get the amount that each share earned during this period + // (total reward - fee) / total share count + const amountPerShare = totalPayoutAmount / BigInt(totalShareCount) + + // The total balance required to send this payout + // (total shares * amount per share) + fee + const totalRequired = amountPerShare * BigInt(totalShareCount) + feeAmount + + // Sanity assertion to make sure the pool is not overpaying + Assert.isTrue( + totalPayoutReward >= totalRequired, + 'Payout total must be less than or equal to the total reward amount', ) + const hasEnoughBalance = await this.hasConfirmedBalance(totalRequired) + if (!hasEnoughBalance) { + this.logger.info('Insufficient funds for payout, skipping.') + return + } + + let sharesInPayout = 0 + + const assetId = Asset.nativeId().toString('hex') + const outputs: { + publicAddress: string + amount: string + memo: string + assetId: string + }[] = [] + for (const payout of payoutAddresses) { + sharesInPayout += payout.shareCount + const amount = amountPerShare * BigInt(payout.shareCount) + outputs.push({ + publicAddress: payout.publicAddress, + amount: CurrencyUtils.encode(amount), + memo: `${this.poolName} payout ${payoutPeriod.id}`, + assetId, + }) + } + try { this.logger.debug( - `Creating payout ${payoutId}, shares: ${shareCounts.totalShares}, outputs: ${transactionReceives.length}`, - ) - this.webhooks.map((w) => - w.poolPayoutStarted(payoutId, transactionReceives, shareCounts.totalShares), + `Creating payout for payout period ${payoutPeriod.id}, shares: ${totalShareCount}, outputs: ${outputs.length}`, ) + this.webhooks.map((w) => w.poolPayoutStarted(payoutPeriod.id, outputs, sharesInPayout)) - const transaction = await this.rpc.sendTransaction({ - fromAccountName: this.accountName, - receives: transactionReceives, - fee: transactionReceives.length.toString(), - }) + const transactionHash = await this.sendTransaction(outputs) + + const transactionId = await this.db.newTransaction(transactionHash, payoutPeriod.id) + Assert.isNotUndefined(transactionId) - await this.db.markPayoutSuccess(payoutId, timestamp, transaction.content.hash) + const addressesPaidOut = payoutAddresses.map((p) => p.publicAddress) + await this.db.markSharesPaid(payoutPeriod.id, transactionId, addressesPaidOut) - this.logger.debug(`Payout ${payoutId} succeeded`) + this.logger.debug(`Payout succeeded with transaction hash ${transactionHash}`) this.webhooks.map((w) => - w.poolPayoutSuccess( - payoutId, - transaction.content.hash, - transactionReceives, - shareCounts.totalShares, - ), + w.poolPayoutSuccess(payoutPeriod.id, transactionHash, outputs, sharesInPayout), ) } catch (e) { this.logger.error(`There was an error with the transaction ${ErrorUtils.renderError(e)}`) @@ -183,40 +262,28 @@ export class MiningPoolShares { } } - sumShares(shares: DatabaseShare[]): { totalShares: number; shares: Map } { - let totalShares = 0 - const shareMap = new Map() - - shares.forEach((share) => { - const address = share.publicAddress - const shareCount = shareMap.get(address) - - if (shareCount != null) { - shareMap.set(address, shareCount + 1) - } else { - shareMap.set(address, 1) - } - - totalShares += 1 - }) - - return { - totalShares, - shares: shareMap, - } - } + async hasConfirmedBalance(amount: bigint): Promise { + const balance = await this.rpc.getAccountBalance({ account: this.accountName }) + const confirmedBalance = BigInt(balance.content.confirmed) - async shareRate(publicAddress?: string): Promise { - return (await this.recentShareCount(publicAddress)) / this.recentShareCutoff + return confirmedBalance >= amount } - private async recentShareCount(publicAddress?: string): Promise { - const timestamp = Math.floor(new Date().getTime() / 1000) - this.recentShareCutoff - - return await this.db.shareCountSince(timestamp, publicAddress) - } + async sendTransaction( + outputs: { + publicAddress: string + amount: string + memo: string + assetId: string + }[], + ): Promise { + const transaction = await this.rpc.sendTransaction({ + fromAccountName: this.accountName, + outputs, + fee: outputs.length.toString(), + expirationDelta: this.config.get('transactionExpirationDelta'), + }) - async sharesPendingPayout(publicAddress?: string): Promise { - return await this.db.getSharesCountForPayout(publicAddress) + return transaction.content.hash } } diff --git a/ironfish/src/mining/stratum/adapters/adapter.ts b/ironfish/src/mining/stratum/adapters/adapter.ts new file mode 100644 index 0000000000..d31b18f91e --- /dev/null +++ b/ironfish/src/mining/stratum/adapters/adapter.ts @@ -0,0 +1,29 @@ +/* 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 { StratumServer } from '../stratumServer' + +/** + * An adapter represents a network transport that accepts connections from + * clients and routes them into the server. + */ +export interface IStratumAdapter { + /** + * Called when the adapter is added to a StratumServer. + */ + attach(server: StratumServer): void + + /** + * Called when the adapter should start serving requests to the server + * This is when an adapter would normally listen on a port for data and + * create {@link Request } for the routing layer. + * + * For example, when an + * HTTP server starts listening, or an IPC layer opens an IPC socket. + */ + start(): Promise + + /** Called when the adapter should stop serving requests to the server. */ + stop(): Promise +} diff --git a/ironfish/src/mining/stratum/adapters/index.ts b/ironfish/src/mining/stratum/adapters/index.ts new file mode 100644 index 0000000000..436cbdef3f --- /dev/null +++ b/ironfish/src/mining/stratum/adapters/index.ts @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +export * from './adapter' +export * from './tcpAdapter' +export * from './tlsAdapter' diff --git a/ironfish/src/mining/stratum/adapters/tcpAdapter.ts b/ironfish/src/mining/stratum/adapters/tcpAdapter.ts new file mode 100644 index 0000000000..3934840bc3 --- /dev/null +++ b/ironfish/src/mining/stratum/adapters/tcpAdapter.ts @@ -0,0 +1,65 @@ +/* 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 net from 'net' +import { Logger } from '../../../logger' +import { StratumServer } from '../stratumServer' +import { IStratumAdapter } from './adapter' + +export class StratumTcpAdapter implements IStratumAdapter { + server: net.Server | null = null + stratumServer: StratumServer | null = null + readonly logger: Logger + + readonly host: string + readonly port: number + + started = false + + constructor(options: { logger: Logger; host: string; port: number }) { + this.logger = options.logger + this.host = options.host + this.port = options.port + } + + protected createServer(): net.Server { + this.logger.info(`Hosting Stratum via TCP on ${this.host}:${this.port}`) + + return net.createServer((socket) => this.stratumServer?.onConnection(socket)) + } + + start(): Promise { + if (this.started) { + return Promise.resolve() + } + + this.started = true + + return new Promise((resolve, reject) => { + try { + this.server = this.createServer() + this.server.listen(this.port, this.host, () => { + resolve() + }) + } catch (e) { + reject(e) + } + }) + } + + stop(): Promise { + if (!this.started) { + return Promise.resolve() + } + + return new Promise((resolve, reject) => { + this.server?.close((e) => { + return e ? reject(e) : resolve() + }) + }) + } + + attach(server: StratumServer): void { + this.stratumServer = server + } +} diff --git a/ironfish/src/mining/stratum/adapters/tlsAdapter.ts b/ironfish/src/mining/stratum/adapters/tlsAdapter.ts new file mode 100644 index 0000000000..c751c16783 --- /dev/null +++ b/ironfish/src/mining/stratum/adapters/tlsAdapter.ts @@ -0,0 +1,30 @@ +/* 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 net from 'net' +import tls from 'tls' +import { Logger } from '../../../logger' +import { StratumTcpAdapter } from './tcpAdapter' + +export class StratumTlsAdapter extends StratumTcpAdapter { + readonly tlsOptions: tls.TlsOptions + + constructor(options: { + logger: Logger + host: string + port: number + tlsOptions: tls.TlsOptions + }) { + super(options) + + this.tlsOptions = options.tlsOptions + } + + protected createServer(): net.Server { + this.logger.info(`Hosting Stratum via TLS on ${this.host}:${this.port}`) + + return tls.createServer(this.tlsOptions, (socket) => + this.stratumServer?.onConnection(socket), + ) + } +} diff --git a/ironfish/src/mining/stratum/stratumClient.ts b/ironfish/src/mining/stratum/clients/client.ts similarity index 74% rename from ironfish/src/mining/stratum/stratumClient.ts rename to ironfish/src/mining/stratum/clients/client.ts index e0bcaabd3f..b3c1773496 100644 --- a/ironfish/src/mining/stratum/stratumClient.ts +++ b/ironfish/src/mining/stratum/clients/client.ts @@ -1,14 +1,14 @@ /* 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 net from 'net' -import { Event } from '../../event' -import { Logger } from '../../logger' -import { ErrorUtils } from '../../utils' -import { SetTimeoutToken } from '../../utils/types' -import { YupUtils } from '../../utils/yup' -import { DisconnectReason } from './constants' -import { ServerMessageMalformedError } from './errors' +import { Event } from '../../../event' +import { Logger } from '../../../logger' +import { MessageBuffer } from '../../../rpc' +import { ErrorUtils } from '../../../utils' +import { SetTimeoutToken } from '../../../utils/types' +import { YupUtils } from '../../../utils/yup' +import { DisconnectReason } from '../constants' +import { ServerMessageMalformedError } from '../errors' import { MiningDisconnectMessageSchema, MiningGetStatusMessage, @@ -26,13 +26,10 @@ import { MiningWaitForWorkSchema, StratumMessage, StratumMessageSchema, -} from './messages' -import { VERSION_PROTOCOL_STRATUM } from './version' +} from '../messages' +import { VERSION_PROTOCOL_STRATUM } from '../version' -export class StratumClient { - readonly socket: net.Socket - readonly host: string - readonly port: number +export abstract class StratumClient { readonly logger: Logger readonly version: number @@ -42,7 +39,7 @@ export class StratumClient { private connectWarned: boolean private connectTimeout: SetTimeoutToken | null private nextMessageId: number - private messageBuffer = '' + private readonly messageBuffer = new MessageBuffer('\n') private disconnectReason: string | null = null private disconnectUntil: number | null = null @@ -56,9 +53,7 @@ export class StratumClient { readonly onWaitForWork = new Event<[MiningWaitForWorkMessage]>() readonly onStatus = new Event<[MiningStatusMessage]>() - constructor(options: { host: string; port: number; logger: Logger }) { - this.host = options.host - this.port = options.port + constructor(options: { logger: Logger }) { this.logger = options.logger this.version = VERSION_PROTOCOL_STRATUM @@ -68,11 +63,12 @@ export class StratumClient { this.connected = false this.connectWarned = false this.connectTimeout = null - - this.socket = new net.Socket() - this.socket.on('data', (data) => void this.onData(data).catch((e) => this.onError(e))) } + protected abstract connect(): Promise + protected abstract writeData(data: string): void + protected abstract close(): Promise + start(): void { if (this.started) { return @@ -89,7 +85,7 @@ export class StratumClient { return } - const connected = await connectSocket(this.socket, this.host, this.port) + const connected = await this.connect() .then(() => true) .catch(() => false) @@ -99,7 +95,7 @@ export class StratumClient { if (!connected) { if (!this.connectWarned) { - this.logger.warn(`Failed to connect to pool at ${this.host}:${this.port}, retrying...`) + this.logger.warn(`Failed to connect to pool, retrying...`) this.connectWarned = true } @@ -113,7 +109,7 @@ export class StratumClient { } stop(): void { - this.socket.end() + void this.close() if (this.connectTimeout) { clearTimeout(this.connectTimeout) @@ -159,22 +155,18 @@ export class StratumClient { body: body, } - this.socket.write(JSON.stringify(message) + '\n') + this.writeData(JSON.stringify(message) + '\n') } - private onConnect(): void { + protected onConnect(): void { this.connected = true - this.socket.on('error', this.onError) - this.socket.on('close', this.onDisconnect) this.logger.info('Successfully connected to pool') } - private onDisconnect = (): void => { + protected onDisconnect = (): void => { this.connected = false - this.messageBuffer = '' - this.socket.off('error', this.onError) - this.socket.off('close', this.onDisconnect) + this.messageBuffer.clear() this.onWaitForWork.emit(undefined) @@ -201,18 +193,15 @@ export class StratumClient { this.connectTimeout = setTimeout(() => void this.startConnecting(), 5000) } - private onError = (error: unknown): void => { + protected onError = (error: unknown): void => { this.logger.error(`Stratum Error ${ErrorUtils.renderError(error)}`) } - private async onData(data: Buffer): Promise { - this.messageBuffer += data.toString('utf-8') - const lastDelimiterIndex = this.messageBuffer.lastIndexOf('\n') - const splits = this.messageBuffer.substring(0, lastDelimiterIndex).trim().split('\n') - this.messageBuffer = this.messageBuffer.substring(lastDelimiterIndex + 1) + protected async onData(data: Buffer): Promise { + this.messageBuffer.write(data) - for (const split of splits) { - const payload: unknown = JSON.parse(split) + for (const message of this.messageBuffer.readMessages()) { + const payload: unknown = JSON.parse(message) const header = await YupUtils.tryValidate(StratumMessageSchema, payload) @@ -234,7 +223,7 @@ export class StratumClient { this.disconnectUntil = body.result?.bannedUntil ?? null this.disconnectMessage = body.result?.message ?? null - this.socket.destroy() + this.stop() break } @@ -300,24 +289,3 @@ export class StratumClient { } } } - -// Transform net.Socket.connect() callback into a nicer promise style interface -function connectSocket(socket: net.Socket, host: string, port: number): Promise { - return new Promise((resolve, reject): void => { - const onConnect = () => { - socket.off('connect', onConnect) - socket.off('error', onError) - resolve() - } - - const onError = (error: unknown) => { - socket.off('connect', onConnect) - socket.off('error', onError) - reject(error) - } - - socket.on('error', onError) - socket.on('connect', onConnect) - socket.connect(port, host) - }) -} diff --git a/ironfish/src/mining/stratum/clients/index.ts b/ironfish/src/mining/stratum/clients/index.ts new file mode 100644 index 0000000000..5a5d770c3a --- /dev/null +++ b/ironfish/src/mining/stratum/clients/index.ts @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +export * from './client' +export * from './tcpClient' +export * from './tlsClient' diff --git a/ironfish/src/mining/stratum/clients/tcpClient.ts b/ironfish/src/mining/stratum/clients/tcpClient.ts new file mode 100644 index 0000000000..8ae6bc617f --- /dev/null +++ b/ironfish/src/mining/stratum/clients/tcpClient.ts @@ -0,0 +1,66 @@ +/* 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 net from 'net' +import { Logger } from '../../../logger' +import { StratumClient } from './client' + +export class StratumTcpClient extends StratumClient { + readonly host: string + readonly port: number + + client: net.Socket | null = null + + constructor(options: { host: string; port: number; logger: Logger }) { + super({ logger: options.logger }) + this.host = options.host + this.port = options.port + } + + protected onSocketDisconnect = (): void => { + this.client?.off('error', this.onError) + this.client?.off('close', this.onDisconnect) + this.client?.off('data', this.onSocketData) + this.onDisconnect() + } + + protected onSocketData = (data: Buffer): void => { + this.onData(data).catch((e) => this.onError(e)) + } + + protected connect(): Promise { + return new Promise((resolve, reject): void => { + const onConnect = () => { + client.off('connect', onConnect) + client.off('error', onError) + + client.on('error', this.onError) + client.on('close', this.onSocketDisconnect) + + resolve() + } + + const onError = (error: unknown) => { + client.off('connect', onConnect) + client.off('error', onError) + reject(error) + } + + const client = new net.Socket() + client.on('error', onError) + client.on('connect', onConnect) + client.on('data', this.onSocketData) + client.connect({ host: this.host, port: this.port }) + this.client = client + }) + } + + protected writeData(data: string): void { + this.client?.write(data) + } + + protected close(): Promise { + this.client?.destroy() + return Promise.resolve() + } +} diff --git a/ironfish/src/mining/stratum/clients/tlsClient.ts b/ironfish/src/mining/stratum/clients/tlsClient.ts new file mode 100644 index 0000000000..6765a8bdfc --- /dev/null +++ b/ironfish/src/mining/stratum/clients/tlsClient.ts @@ -0,0 +1,37 @@ +/* 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 tls from 'tls' +import { StratumTcpClient } from './tcpClient' + +export class StratumTlsClient extends StratumTcpClient { + protected connect(): Promise { + return new Promise((resolve, reject): void => { + const onConnect = () => { + client.off('secureConnect', onConnect) + client.off('error', onError) + + client.on('error', this.onError) + client.on('close', this.onSocketDisconnect) + + resolve() + } + + const onError = (error: unknown) => { + client.off('secureConnect', onConnect) + client.off('error', onError) + reject(error) + } + + const client = tls.connect({ + host: this.host, + port: this.port, + rejectUnauthorized: false, + }) + client.on('error', onError) + client.on('secureConnect', onConnect) + client.on('data', this.onSocketData) + this.client = client + }) + } +} diff --git a/ironfish/src/mining/stratum/index.ts b/ironfish/src/mining/stratum/index.ts index d59c5719bd..89b428e489 100644 --- a/ironfish/src/mining/stratum/index.ts +++ b/ironfish/src/mining/stratum/index.ts @@ -2,6 +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/. */ -export { StratumClient } from './stratumClient' +export * from './clients' export { StratumServer } from './stratumServer' export { MiningStatusMessage } from './messages' diff --git a/ironfish/src/mining/stratum/stratumServer.ts b/ironfish/src/mining/stratum/stratumServer.ts index d759a94d6b..d6183c773c 100644 --- a/ironfish/src/mining/stratum/stratumServer.ts +++ b/ironfish/src/mining/stratum/stratumServer.ts @@ -13,6 +13,7 @@ import { YupUtils } from '../../utils/yup' import { isValidPublicAddress } from '../../wallet/validator' import { MiningPool } from '../pool' import { mineableHeaderString } from '../utils' +import { IStratumAdapter } from './adapters' import { DisconnectReason } from './constants' import { ClientMessageMalformedError } from './errors' import { @@ -34,14 +35,11 @@ import { VERSION_PROTOCOL_STRATUM, VERSION_PROTOCOL_STRATUM_MIN } from './versio const FIVE_MINUTES_MS = 5 * 60 * 1000 export class StratumServer { - readonly server: net.Server readonly pool: MiningPool readonly config: Config readonly logger: Logger readonly peers: StratumPeers - - readonly port: number - readonly host: string + readonly adapters: IStratumAdapter[] = [] clients: Map nextMinerId: number @@ -53,12 +51,13 @@ export class StratumServer { readonly version: number readonly versionMin: number + private _isRunning = false + private _startPromise: Promise | null = null + constructor(options: { pool: MiningPool config: Config logger: Logger - port?: number - host?: string banning?: boolean }) { this.pool = options.pool @@ -68,9 +67,6 @@ export class StratumServer { this.version = VERSION_PROTOCOL_STRATUM this.versionMin = VERSION_PROTOCOL_STRATUM_MIN - this.host = options.host ?? this.config.get('poolHost') - this.port = options.port ?? this.config.get('poolPort') - this.clients = new Map() this.nextMinerId = 1 this.nextMessageId = 1 @@ -81,18 +77,55 @@ export class StratumServer { server: this, banning: options.banning, }) + } - this.server = net.createServer((s) => this.onConnection(s)) + get isRunning(): boolean { + return this._isRunning } - start(): void { + /** Starts the Stratum server and tells any attached adapters to start serving requests */ + async start(): Promise { + if (this._isRunning) { + return + } + this.peers.start() - this.server.listen(this.port, this.host) + + this._startPromise = Promise.all(this.adapters.map((a) => a.start())) + this._isRunning = true + await this._startPromise + } + + /** Stops the Stratum server and tells any attached adapters to stop serving requests */ + async stop(): Promise { + if (!this._isRunning) { + return + } + + if (this._startPromise) { + await this._startPromise + } + + await Promise.all(this.adapters.map((a) => a.stop())) + this._isRunning = false } - stop(): void { - this.peers.stop() - this.server.close() + /** Adds an adapter to the Stratum server and starts it if the server has already been started */ + mount(adapter: IStratumAdapter): void { + this.adapters.push(adapter) + adapter.attach(this) + + if (this._isRunning) { + let promise: Promise = adapter.start() + + if (this._startPromise) { + // Attach this promise to the start promise chain + // in case we call stop while were still starting up + promise = Promise.all([this._startPromise, promise]) + } + + this._startPromise = promise + } } newWork(miningRequestId: number, block: SerializedBlockTemplate): void { @@ -116,7 +149,7 @@ export class StratumServer { return this.currentWork != null } - private onConnection(socket: net.Socket): void { + onConnection(socket: net.Socket): void { if (!this.peers.isAllowed(socket)) { if (this.peers.isBanned(socket)) { this.peers.sendBanMessage(socket) diff --git a/ironfish/src/mining/webhooks/webhookNotifier.ts b/ironfish/src/mining/webhooks/webhookNotifier.ts index 541e12ccbe..f51c412663 100644 --- a/ironfish/src/mining/webhooks/webhookNotifier.ts +++ b/ironfish/src/mining/webhooks/webhookNotifier.ts @@ -52,20 +52,20 @@ export abstract class WebhookNotifier { } poolPayoutSuccess( - payoutId: number, + payoutPeriodId: number, transactionHashHex: string, - receives: { publicAddress: string; amount: string; memo: string }[], - totalShareCount: number, + outputs: { publicAddress: string; amount: string; memo: string }[], + shareCount: number, ): void { - const total = receives.reduce((m, c) => BigInt(c.amount) + m, BigInt(0)) + const total = outputs.reduce((m, c) => BigInt(c.amount) + m, BigInt(0)) this.sendText( - `Successfully created payout of ${totalShareCount} shares to ${ - receives.length + `Successfully created payout of ${shareCount} shares to ${ + outputs.length } users for ${CurrencyUtils.renderIron(total, true)} in transaction ${this.renderHashHex( transactionHashHex, this.explorerTransactionsUrl, - )}. Transaction pending (${payoutId})`, + )}. Transaction pending (${payoutPeriodId})`, ) } @@ -76,16 +76,16 @@ export abstract class WebhookNotifier { } poolPayoutStarted( - payoutId: number, - receives: { publicAddress: string; amount: string; memo: string }[], - totalShareCount: number, + payoutPeriodId: number, + outputs: { publicAddress: string; amount: string; memo: string }[], + shareCount: number, ): void { - const total = receives.reduce((m, c) => BigInt(c.amount) + m, BigInt(0)) + const total = outputs.reduce((m, c) => BigInt(c.amount) + m, BigInt(0)) this.sendText( - `Creating payout of ${totalShareCount} shares to ${ - receives.length - } users for ${CurrencyUtils.renderIron(total, true)} (${payoutId})`, + `Creating payout of ${shareCount} shares to ${ + outputs.length + } users for ${CurrencyUtils.renderIron(total, true)} (${payoutPeriodId})`, ) } diff --git a/ironfish/src/network/peerNetwork.ts b/ironfish/src/network/peerNetwork.ts index d6a18860d4..ff19024e77 100644 --- a/ironfish/src/network/peerNetwork.ts +++ b/ironfish/src/network/peerNetwork.ts @@ -403,6 +403,11 @@ export class PeerNetwork { } } + /** + * Send out the transaction only to peers that have not yet heard about it. + * The full transaction will be sent to a subset of sqrt(num_peers) + * and the rest of the peers will receive the transaction hash + */ private broadcastTransaction(transaction: Transaction): void { const hash = transaction.hash() @@ -960,9 +965,10 @@ export class PeerNetwork { for (const hash of message.hashes) { peer.state.identity && this.markKnowsTransaction(hash, peer.state.identity) - // If the transaction is already in the mempool the only thing we have to do is broadcast + // If the transaction is already in the mempool we don't need to request + // the full transaction. Just broadcast it const transaction = this.node.memPool.get(hash) - if (transaction && !this.alreadyHaveTransaction(hash)) { + if (transaction) { this.broadcastTransaction(transaction) } else { this.transactionFetcher.hashReceived(hash, peer) @@ -1356,33 +1362,36 @@ export class PeerNetwork { // Let the fetcher know that a transaction was received and we no longer have to query it this.transactionFetcher.receivedTransaction(hash) - if (this.shouldProcessTransactions() && !this.alreadyHaveTransaction(hash)) { - // Check that the transaction is valid - const { valid, reason } = await this.chain.verifier.verifyNewTransaction(transaction) + if (!this.shouldProcessTransactions() || this.alreadyHaveTransaction(hash)) { + this.transactionFetcher.removeTransaction(hash) + return + } - if (!valid) { - Assert.isNotUndefined(reason) - // Logging hash because unsignedHash is slow - this.logger.debug(`Invalid transaction '${hash.toString('hex')}': ${reason}`) - this.transactionFetcher.removeTransaction(hash) - return - } + // Check that the transaction is valid + const { valid, reason } = await this.chain.verifier.verifyNewTransaction(transaction) - if (this.node.memPool.acceptTransaction(transaction)) { - this.onTransactionAccepted.emit(transaction, received) - } + if (!valid) { + Assert.isNotUndefined(reason) + // Logging hash because unsignedHash is slow + this.logger.debug(`Invalid transaction '${hash.toString('hex')}': ${reason}`) + this.transactionFetcher.removeTransaction(hash) + return + } - // Check 'exists' rather than 'accepted' to allow for rebroadcasting to nodes that - // may not have seen the transaction yet - if (this.node.memPool.exists(transaction.hash())) { - this.broadcastTransaction(transaction) - } + if (this.node.memPool.acceptTransaction(transaction)) { + this.onTransactionAccepted.emit(transaction, received) + } - // Sync every transaction to the wallet, since senders and recipients may want to know - // about pending transactions even if they're not accepted to the mempool. - await this.node.wallet.addPendingTransaction(transaction) + // Check 'exists' rather than 'accepted' to allow for rebroadcasting to nodes that + // may not have seen the transaction yet + if (this.node.memPool.exists(transaction.hash())) { + this.broadcastTransaction(transaction) } + // Sync every transaction to the wallet, since senders and recipients may want to know + // about pending transactions even if they're not accepted to the mempool. + await this.node.wallet.addPendingTransaction(transaction) + this.transactionFetcher.removeTransaction(hash) } } diff --git a/ironfish/src/network/peers/peer.ts b/ironfish/src/network/peers/peer.ts index b60f7522d6..714edb571a 100644 --- a/ironfish/src/network/peers/peer.ts +++ b/ironfish/src/network/peers/peer.ts @@ -194,7 +194,7 @@ export class Peer { /** * Fired when the peer should be banned */ - readonly onBanned: Event<[]> = new Event() + readonly onBanned: Event<[string]> = new Event() /** * Event fired when the peer changes state. The event may fire when connections change, even if the @@ -704,7 +704,7 @@ export class Peer { } this.logger.info(`Peer ${this.displayName} has been banned: ${reason || 'UNKNOWN'}`) - this.onBanned.emit() + this.onBanned.emit(reason || 'UNKNOWN') this.close(new Error(`BANNED: ${reason || 'UNKNOWN'}`)) return true } diff --git a/ironfish/src/network/peers/peerManager.test.ts b/ironfish/src/network/peers/peerManager.test.ts index 67dc1a2822..7f6db72dac 100644 --- a/ironfish/src/network/peers/peerManager.test.ts +++ b/ironfish/src/network/peers/peerManager.test.ts @@ -226,7 +226,7 @@ describe('PeerManager', () => { const { peer } = getConnectedPeer(peers) - peers.banPeer(peer) + peers.banPeer(peer, 'UNKNOWN') expect(peers.banned.has(peer.getIdentityOrThrow())).toBe(true) }) diff --git a/ironfish/src/network/peers/peerManager.ts b/ironfish/src/network/peers/peerManager.ts index e84ccca254..89e0860858 100644 --- a/ironfish/src/network/peers/peerManager.ts +++ b/ironfish/src/network/peers/peerManager.ts @@ -66,7 +66,8 @@ export class PeerManager { */ readonly identifiedPeers: Map = new Map() - readonly banned = new Set() + // Mapping of peer identity with reason they were banned + readonly banned = new Map() /** * List of all peers, including both unidentified and identified. @@ -785,16 +786,16 @@ export class PeerManager { } }) - peer.onBanned.on(() => this.banPeer(peer)) + peer.onBanned.on((reason) => this.banPeer(peer, reason)) return peer } - banPeer(peer: Peer): void { + banPeer(peer: Peer, reason: string): void { const identity = peer.state.identity if (identity) { - this.banned.add(identity) + this.banned.set(identity, reason) } peer.close() diff --git a/ironfish/src/primitives/rawTransaction.test.ts b/ironfish/src/primitives/rawTransaction.test.ts index 6e476b64d7..50c949d822 100644 --- a/ironfish/src/primitives/rawTransaction.test.ts +++ b/ironfish/src/primitives/rawTransaction.test.ts @@ -229,7 +229,7 @@ describe('RawTransactionSerde', () => { }, ] - raw.receives = [ + raw.outputs = [ { note: note, }, @@ -246,7 +246,7 @@ describe('RawTransactionSerde', () => { }) expect(RawTransactionSerde.serialize(deserialized).equals(serialized)).toBe(true) - expect(deserialized.receives[0].note).toEqual(raw.receives[0].note) + expect(deserialized.outputs[0].note).toEqual(raw.outputs[0].note) expect(deserialized.burns[0].assetId).toEqual(asset.id()) expect(deserialized.burns[0].value).toEqual(5n) expect(deserialized.mints[0].name).toEqual(assetName) diff --git a/ironfish/src/primitives/rawTransaction.ts b/ironfish/src/primitives/rawTransaction.ts index 66f2239e35..6fa7edd3f9 100644 --- a/ironfish/src/primitives/rawTransaction.ts +++ b/ironfish/src/primitives/rawTransaction.ts @@ -33,7 +33,7 @@ export class RawTransaction { fee = 0n mints: MintData[] = [] burns: BurnDescription[] = [] - receives: { note: Note }[] = [] + outputs: { note: Note }[] = [] spends: { note: Note @@ -53,9 +53,9 @@ export class RawTransaction { spend.note.returnReference() } - for (const receive of this.receives) { - builder.receive(receive.note.takeReference()) - receive.note.returnReference() + for (const output of this.outputs) { + builder.output(output.note.takeReference()) + output.note.returnReference() } for (const mint of this.mints) { @@ -113,15 +113,15 @@ export class RawTransactionSerde { } } - bw.writeU64(raw.receives.length) - for (const receive of raw.receives) { - bw.writeVarBytes(receive.note.serialize()) + bw.writeU64(raw.outputs.length) + for (const output of raw.outputs) { + bw.writeVarBytes(output.note.serialize()) } bw.writeU64(raw.mints.length) for (const mint of raw.mints) { - bw.writeVarString(mint.name) - bw.writeVarString(mint.metadata) + bw.writeVarString(mint.name, 'utf8') + bw.writeVarString(mint.metadata, 'utf8') bw.writeBigU64(mint.value) } @@ -164,16 +164,16 @@ export class RawTransactionSerde { raw.spends.push({ note, witness }) } - const receivesLength = reader.readU64() - for (let i = 0; i < receivesLength; i++) { + const outputsLength = reader.readU64() + for (let i = 0; i < outputsLength; i++) { const note = new Note(reader.readVarBytes()) - raw.receives.push({ note }) + raw.outputs.push({ note }) } const mintsLength = reader.readU64() for (let i = 0; i < mintsLength; i++) { - const name = reader.readVarString() - const metadata = reader.readVarString() + const name = reader.readVarString('utf8') + const metadata = reader.readVarString('utf8') const value = reader.readBigU64() raw.mints.push({ name, metadata, value }) } @@ -211,9 +211,9 @@ export class RawTransactionSerde { } } - size += 8 // raw.receives.length - for (const receive of raw.receives) { - size += bufio.sizeVarBytes(receive.note.serialize()) + size += 8 // raw.outputs.length + for (const output of raw.outputs) { + size += bufio.sizeVarBytes(output.note.serialize()) } size += 8 // raw.mints.length diff --git a/ironfish/src/rpc/adapters/tlsAdapter.ts b/ironfish/src/rpc/adapters/tlsAdapter.ts index 49678f289f..36098b6f2a 100644 --- a/ironfish/src/rpc/adapters/tlsAdapter.ts +++ b/ironfish/src/rpc/adapters/tlsAdapter.ts @@ -3,11 +3,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { randomBytes } from 'crypto' import net from 'net' -import { pki } from 'node-forge' import tls from 'tls' import { FileSystem } from '../../fileSystems' import { createRootLogger, Logger } from '../../logger' import { IronfishNode } from '../../node' +import { TlsUtils } from '../../utils/tls' import { ApiNamespace } from '../routes' import { RpcSocketAdapter } from './socketAdapter/socketAdapter' @@ -36,13 +36,6 @@ export class RpcTlsAdapter extends RpcSocketAdapter { } protected async createServer(): Promise { - const options = await this.getTlsOptions() - return tls.createServer(options, (socket) => this.onClientConnection(socket)) - } - - protected async getTlsOptions(): Promise { - const nodeKeyExists = await this.fileSystem.exists(this.nodeKeyPath) - const nodeCertExists = await this.fileSystem.exists(this.nodeCertPath) const rpcAuthToken = this.node.internal.get('rpcAuthToken') if (!rpcAuthToken || rpcAuthToken === '') { @@ -54,38 +47,13 @@ export class RpcTlsAdapter extends RpcSocketAdapter { await this.node.internal.save() } - if (!nodeKeyExists || !nodeCertExists) { - this.logger.debug( - `Missing TLS key and/or cert files at ${this.nodeKeyPath} and ${this.nodeCertPath}. Automatically generating key and self-signed cert`, - ) - - return await this.generateTlsCerts() - } - - return { - key: await this.fileSystem.readFile(this.nodeKeyPath), - cert: await this.fileSystem.readFile(this.nodeCertPath), - } - } - - protected async generateTlsCerts(): Promise { - const keyPair = pki.rsa.generateKeyPair(2048) - const cert = pki.createCertificate() - cert.publicKey = keyPair.publicKey - cert.sign(keyPair.privateKey) - - const nodeKeyPem = pki.privateKeyToPem(keyPair.privateKey) - const nodeCertPem = pki.certificateToPem(cert) + const options = await TlsUtils.getTlsOptions( + this.fileSystem, + this.nodeKeyPath, + this.nodeCertPath, + this.logger, + ) - const nodeKeyDir = this.fileSystem.dirname(this.nodeKeyPath) - const nodeCertDir = this.fileSystem.dirname(this.nodeCertPath) - - await this.fileSystem.mkdir(nodeKeyDir, { recursive: true }) - await this.fileSystem.mkdir(nodeCertDir, { recursive: true }) - - await this.fileSystem.writeFile(this.nodeKeyPath, nodeKeyPem) - await this.fileSystem.writeFile(this.nodeCertPath, nodeCertPem) - - return { key: nodeKeyPem, cert: nodeCertPem } + return tls.createServer(options, (socket) => this.onClientConnection(socket)) } } diff --git a/ironfish/src/rpc/clients/client.ts b/ironfish/src/rpc/clients/client.ts index 17248eef97..b1fab778bb 100644 --- a/ironfish/src/rpc/clients/client.ts +++ b/ironfish/src/rpc/clients/client.ts @@ -79,6 +79,7 @@ import { FollowChainStreamResponse, } from '../routes/chain/followChain' import { OnGossipRequest, OnGossipResponse } from '../routes/events/onGossip' +import { GetBannedPeersRequest, GetBannedPeersResponse } from '../routes/peers/getBannedPeers' import { GetPeerRequest, GetPeerResponse } from '../routes/peers/getPeer' import { GetPeerMessagesRequest, @@ -92,6 +93,7 @@ import { CreateTransactionResponse, } from '../routes/wallet/createTransaction' import { ExportAccountRequest, ExportAccountResponse } from '../routes/wallet/exportAccount' +import { GetAssetsRequest, GetAssetsResponse } from '../routes/wallet/getAssets' import { GetBalancesRequest, GetBalancesResponse } from '../routes/wallet/getBalances' import { GetAccountStatusRequest, GetAccountStatusResponse } from '../routes/wallet/getStatus' import { ImportAccountRequest, ImportAccountResponse } from '../routes/wallet/importAccount' @@ -268,6 +270,24 @@ export abstract class RpcClient { ) } + async getBannedPeers( + params: GetBannedPeersRequest = undefined, + ): Promise> { + return this.request( + `${ApiNamespace.peer}/getBannedPeers`, + params, + ).waitForEnd() + } + + getBannedPeersStream( + params: GetBannedPeersRequest = undefined, + ): RpcResponse { + return this.request(`${ApiNamespace.peer}/getBannedPeers`, { + ...params, + stream: true, + }) + } + async getPeers( params: GetPeersRequest = undefined, ): Promise> { @@ -519,6 +539,10 @@ export abstract class RpcClient { return this.request(`${ApiNamespace.chain}/getAsset`, params).waitForEnd() } + getAssets(params: GetAssetsRequest): RpcResponse { + return this.request(`${ApiNamespace.wallet}/getAssets`, params) + } + async postTransaction( params: PostTransactionRequest, ): Promise> { diff --git a/ironfish/src/rpc/routes/chain/__fixtures__/getNetworkHashPower.test.ts.fixture b/ironfish/src/rpc/routes/chain/__fixtures__/getNetworkHashPower.test.ts.fixture index 5eb1c0630e..ccd672e4e1 100644 --- a/ironfish/src/rpc/routes/chain/__fixtures__/getNetworkHashPower.test.ts.fixture +++ b/ironfish/src/rpc/routes/chain/__fixtures__/getNetworkHashPower.test.ts.fixture @@ -326,5 +326,269 @@ } ] } + ], + "Route chain/getNetworkHashPower should succeed with negative sequence value": [ + { + "header": { + "sequence": 7, + "previousBlockHash": "BD73F4D8B10953406501C9BC196BB2B51492E4C29D93CA104621001850D70C94", + "noteCommitment": { + "type": "Buffer", + "data": "base64:10xHte1/QctbEd7GzDPfSfuJVNOczFk0tcrdIdZKb3A=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:YpRjEHoOtSbqbfTV+zI0jIAEtERa5J39wunFPcmHorU=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675703008966, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 9, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA4QDXMDb0+nJEUet41OUE0XAJ0BMgE2tRG/Ux1nIIngepyt3IO8eBssHcmjWF0hAdf+mV6gZWR13B6+deiA+p7Op5PjXNQe5/CUza9ZNyamOlQzNk+eonJ7EgAfRAEXjL5gDirXrZfaiYgsoPUTzYO9U9GXGmQXEbj+xKQxivIikPFplcQaO8jZrjkrnS4+GcNRcpU243d35PkJoROYZXn3AK54NBMMhkkHe1TTAh03+v5+MoBmARAr5TjZwTeFd3VEi/jGttcTbPCUT0ng75h7kmPlavpxi9fAQMOjiVlawlc4MXiuo8ECTNasOxEl5Rjc1NIJb9cFobTidcLKdXSkiMc5RJDdlKNpUC0YCyHmE7Gd1iLV/K7tpLmhiAtEwGO6NOOBjR9e65JXecVaoSRcho1P75dO/T7czRfM97p7/J/zHR4gQvU4mtnXcQA6M5WwhA9phczVJgxemBpxZyYECRuGUn0eO0xgU19enwobbN96fHR41VFiU8s/+zW0GtswuU5zKMo8TePjYdYDVr4copQa8bJkomyUXRdQ1qHIjk75scufRaHT4B9bu16eZfq0f17kxooKv5XQxIIO7kd7FF3bVQNG3LIJzzoKcXGULmrNHMNJAxsklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwdp3qp/RyLOnGzJ15i0gkDZzunPgQcAPbhd2ZdwbnbokplCZqdMzbqfuHPfohYwI09YQozeT6ASeYPZYdweBLBQ==" + } + ] + }, + { + "header": { + "sequence": 8, + "previousBlockHash": "B143A21A830F696925EFFE1A396BA38FA1EC33D9342669918D88B95536994879", + "noteCommitment": { + "type": "Buffer", + "data": "base64:POW1CN3ZZo1Dpt1dEiJxLG/Sl5MHTWWNJV7RqIQJxWA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:8K0No9kJM/IL/qJG/ACjhuBnHWQnTWJmr8hPhmqMBf4=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675703009220, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 10, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA1nDe2anmx8crTmtL/NVuA59kJMVsTFUDATy0qroX07WMjln/4Y69p23W6E0YcOqMXBGxc7POjfGLq+KYSq4GtIztff/Ze/sF+xQYyxTRAzaxwOXwKHNDSOi4D4QndX1FhwWmoc6p0qQIMbTrLQlS3SSPfo1NWaHL0gmzTscbB84Rh/ZT0SE1DjadULreQRAeH/vtbsH2JIK1k+0ceJqxl7O3Nzw3V2WyV25Ay4wDES+Z0bKWw01y6K6U5Icp57U6PJfHMPWX+y6pUPDplfl53E3Jo2K2xQ7LGfQUz8RBhQC1sLQuoM+coLk7YIAiKXvU48zfBVfH3nqxi7xtz8iDnVU3olK72DqB33OhshQzons2E++aH3sWI4yIpNyYR50vemga5M10VEe67oH3jMO41VUd2JfFPZ6xiCfh3/y0JeuVF5RJvcIjh0tV8WjfLhrytPlQ4LIjo8ZNiO0xtJkCUYQQgyyMMMbfYpkgtTus2LqABc7+KGLHCd0tRY8jX9XHla89nmMpALAIyZSlKYYUq43XhUxwt9RmF7D4O+bVFxyVROj5IKR7HFXs9xNBjekk1HOwqIx8p89cdILckKmkZOBAe1oorhHb7nOnntTudtOyIdXMCg3xU0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw1/UnliejyUBe/OWdiHYLvwkSlacUhFDYMZCHROX0357D68Bcbz6AJ2Crjvd8SBzYytyi7rlisSZUxbuKH/AZCQ==" + } + ] + }, + { + "header": { + "sequence": 9, + "previousBlockHash": "BAD3797A4D989AF6B7A29CC19573BC711B5DCC1BA082958F4F1A0944516A25E7", + "noteCommitment": { + "type": "Buffer", + "data": "base64:eOYcXkZVo55cwFeucjkxuVMRmOq/me1lvT4fUwT3+iM=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:F72PqT8mQxrflKw9EO/03+swCLxh029r4kYBm6icP0Y=" + }, + "target": "878277375889837647326843029495509009809390053592540685978895509768758568", + "randomness": "0", + "timestamp": 1675703009469, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 11, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAeCtrGZGc+JItaxv+mImVtE+K1x/1FJtytCQkSTC2QOmDqL7BSGepPJX1kIR/7A/44DP7KTwQF99kdYzmIYadwp/RFAlycT0PVryMfN3jJ26R5wZpu0S2MH579nZuNGi5bd0ELQlqWBWOlgbP0HMu+nqTKmrhaQ5SAVED4RBCz8UAKLSr03RIGsqRgbiwsNAv5k7gf2vL6MIr7E18Sk2EReq2EG+8p3Kmny9F9tzDzAePyJqVSyOJiBZR2xfDgs3MJUrWa1KpTB6aHvk1L4ugZ/Ns3E30iQTJ3LmzGYFbWMNs3fqXYUw17jVvQO9CDxucLMSAHSjvl78cs7Bt5bYWMGEDoMsPTfHWy2WX3mrdU0UVY4a8ycgF9PaRxCLTwKVStDLD4CjaTYA9y8CcZcS2nrQKTehQoQLfHOcXqQRh0m7thF8bO06J3G/EBpG5IHXaX74+mld7uPorEn9tIUaZEv6kl6hoSAy6lItaYCUpLE2uHcuhh02GdaLw53Bznl1YHLQeNlAwOgUtf+QL+9f39wqQfD2q0funwlVWCn28SLRhW3z91Ac3Km52/jdG0rF9HTpipCFe4WirgEO2lYhd+MSQijp3MyJaaPeGjwObmhNJKc7D2E1a+Elyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwH1osucLBBzoS0zU1IaoyL1ZXMZ110jyHAqGhwQqiOdZ2luj/dKAGaqveT0Y5rOhmG6Wg0qInDcqsPDgpimg+Bw==" + } + ] + }, + { + "header": { + "sequence": 10, + "previousBlockHash": "9D1680E073ED1599173595F854D294C515128FBAB50774FCE820CC9EE3343497", + "noteCommitment": { + "type": "Buffer", + "data": "base64:Of5ZvPGZpOjH/td8WoLckxrhQAgGytmfXA0utS1YST0=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:ny/x7zjDJSyAr+7wQoejivXOo5ZXmr1nB/zh/NGWWNs=" + }, + "target": "875726715553274711274586950997458160797358911132930209640137826778142618", + "randomness": "0", + "timestamp": 1675703009728, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 12, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAAyD2/2Tzq9Bf/WC4FjzsEwhuxH0JamjY3xcoByJBJyGGobAY32YrWK3h+MI94pFf1teOD0f8UUWnTPQ6q2ZZn07lx8KhXIMT33o2clxOyCmnw4CxHBBQz54TiL+FQmUUETMnJBH4dRVhOCA/gXLKXbpzKF4XAyU/UQp95rk6K7EFQRSVRC0lFsl9hLUtc9BUG9TN6vx7R5u/JxP8St9B1zQPnfZZyfkCvFI05ykB4MSXpiGUdoqTol2aQ5lrITdrLKxWsIvz9vuldehxfrzJ4nhfZjOnZSlMjak8RtxC2ku/ZA9ewqmmPc5n40zU5kMcoMbXnY/sUtywWNuVoj88YFQ6x0Z5DSQ/3NqMpeaMdtZISIvtW5LATjp7N3eiF203btkfe6DrEnpbCs3ayBOybi8jJexqRsRwM0X4eGmPxTFkJZo1bc3hTOrTKv+ivkvAx3oBpmH35rWz/Y23/tyRt/cBJTftLDOzSNX0SihSYWDDDqLIZYxqc86Ta16zPVo6hRWimnVLuY/q2Tu17OximQFKNPRNaMQ7C1JZOi9wL7Cy2vp4fv6W0aaXk1HbyHHRV+rDcSZ7Y3unCDlhlhxQfVKCRkHAJ0Foquja3AD9c3DMAnaG2Ayl4klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwj1Gm6XihEGvmcMx0mjQ47bPFRbiWtjsTwiaxnud/A7yBuEBoadXtF6yc3emJRcq/pbfnBS1OSCSa72w/2dukCw==" + } + ] + }, + { + "header": { + "sequence": 11, + "previousBlockHash": "04C5B2DE7AA85EEC36E894620407171C3CB3C644E666C2FB8686A5BA1BB3431A", + "noteCommitment": { + "type": "Buffer", + "data": "base64:+S2ShsXlKqAtMgm3PNoczSRAX3JH7IWI4B2FgOVqeDA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:MR13tQYB8Ixsu6iBE0556dkYpyY8AiMr6SRM1w7Fbgg=" + }, + "target": "873190827380823143577845869093025366895436057143163037218399975928398962", + "randomness": "0", + "timestamp": 1675703009981, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 13, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA+SEt73Ts7nwnlK6aH2k3PAGWe/q27gWTroTteWv0muyBFIMwf85ADJfw/WFibfcLX4VEDBw1TOD1GnokShSA2FRBVa2TxqdiIw1M+Ad8VHyH9lzuyHPWZtn9g+EChRNxukYvbMeGhH7/7CSLz94eeMzQ32E/rb6onfloFrZUc0oWS8uIUF6Q6l+9tuhvPXElLU828SK/pVh5+N1dRKfO1TBMf7W+FgwgmCo4SZGcXvGnI7le4yOFglsLrwf6ZXuMj3rFafri66tufkgY9Kmh0sQvw/nahyr3lSGfL7Gj7SPWFGDLos59dzGlUvsEd4+atJtasiTGVj+FFtptFWr/hO9v+KQPiKUH39d+VUDCqbeJ8HcDHHDRCvHf45Snn+4D5GNW2WLGdWs57arGnWyNDsBZHMzQO05PmEcgc1Jvo6p/5RO3prJooKnezvWrbwsdc0pBb6GGrcijwNMEqgZKsonREbcA26U5mzCHL/g4exmROFJmhtuiGoIrF5aW0QDOoQdw7x6yja5K0NnYLpU5f846+CLVIqwr4qNI62TwzXyDxs6eXZiJf4AZD6slzrRawW6AgQT1ecbOAykp6J6y+jJCRPuo1UFgxDsEz1VirqlsxE9VzGWedklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwXxkYmt/jwLrTZjcI/A4SeY9V1rvEN7bLFesPD4qyWyw9MHCAassBVn9HeG9ANazf9teLNZZFIrLJ11n1rAgFAw==" + } + ] + } + ], + "Route chain/getNetworkHashPower should succeed with valid negative sequence value": [ + { + "header": { + "sequence": 7, + "previousBlockHash": "BD73F4D8B10953406501C9BC196BB2B51492E4C29D93CA104621001850D70C94", + "noteCommitment": { + "type": "Buffer", + "data": "base64:UK+rf257UT1JZ0TLShFrJanUSWeoeqUbGJQWmBSEGEY=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:FygF44OllvSGGMorbqMpppNfoPyiUCMdjHVaibUbDKQ=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675703322600, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 9, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAkEJOA9ZC3opgEfXmbVTrne1pdyAd/CF98njfqVmLrV+HHACZEr0VIyHRCwQsnxh3usO/tYf6v9e/kcVVKU4lbrXhtEQgwHF3HJ5BqTVvrEmzqPdGw3xN1iySb2yzDO/zQ9nJet/8GiezV00DdLufLWyV6fMVqJP3l58psmMGmC8A79F+4/UCnYhTyWjsva+ykW/ver1JJ9wuAWaqB+r8FyuyAryuk6pAiFaYl7nGsViPj3VadVJFlAqE7CQqDj1BSnmAWDodu+6ecdfyQhE35CUgGe9f+ZhAi9sL5EBMrzSqUPZn88cc6ul0+x0B9L404AHQONpuOZcnng+3PErcMS1PynC2SgTg+o55fTu2YPYwUa2xCwq5RSc0nqMZHlICTULwtN/jnP9kiPW5Guo5VLxy7hbx6wlmfQcVkNNd1yN1Ltk0mzHhjGnvb8o5zEUss3ujnEVPraOY6D+a9kb4wkDJwjYwnHAsQOwIpw54xSStLh74V2eMlDZ0hvrT8i24Gmp3a9ZtruLrTdrs1EetfQjWMaKDVKjtqjafwJB08YqDlhAc6nj7US8JR2ICTVO7sapEd/XLLBofTgJY4jRl0MyNjENalfhHTdNZh5MoV4sCVypIkQEKgklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwqdprOouwC/GcMXhUbzBCSC39MoWd+bD2UN4NKQfeBxxXlzW3l7MTGbfwTW0a+eq19CbmZfB3ZLBzdllLXbkjBw==" + } + ] + }, + { + "header": { + "sequence": 8, + "previousBlockHash": "283E03F8CEE909DF43C101DB0E12F3259D051643E4D7CD3EB40A542F23E5B078", + "noteCommitment": { + "type": "Buffer", + "data": "base64:ZsC8O+LfYtbrdxNFbbsFU+TdiOfduDM+/0Nicw5AVyI=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:fSLk9gMc6fWFdMG5g3dfiNphMrBQmI+5EaaaUIW1z3I=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675703322963, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 10, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAT8vfQqerUu3V1ddykypBEy4Yvlboj/bPnBx0hDj44XGMwI58FTx7WDzmLvSugGBAj5h0vgpptSjWqwIEOyh6ddRNKQlcZ+Hupwd6M6echcSh3bdqU1qEKxQribIgQ+C9AhMEdg6j77hOhbqOKbGHFUe4PFigNpaXyLr3tc0Rs6AB07fBelSwZcOi/gaweLV/qOJ2EmcKI0YBDkpLmpbfwpzFm2/IsOTXXVHZIaStQJOo96eUXzIHyKGPJThqZxrtMGQar3u4uP0ClQuXWMl1042i818hdSDWEbXAfnk2+n+wIkVqr+n2apHU2aat1XFZFVBIiBD3oWNrAMcbeAcmwXoEUwkwKTji0039hMA3kLruaspH50xZAY30t11Df0pdq4PndIMatQrrendmJCisUhjLoT9Lj1kpR536KC195zyHQFLGvUvnYarNrgCCXZv6DdAyCtQxQjfTALwbnFBdIbFaDPSLBh5UANjfA3nbbg8shLZyGVRdu23TqC8+Gfj48KAiVNoAVeyLR1J33PW3m9KBd4qMg3Qk1bejWf/yBOQziSiEEJAT14si5sa5uWue12Pcv3SYxWGxiJTrml7I6+HrMfpQWhB5mAvzRp3Rc7bFWPgwrYqLa0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwdmisl3dkDLZunx0Mad+bRlr6O6+nJpBaDSOiiGKbO1KwU/ZrXg9NxG45bGRGZ33rg+A3u4FBwSUDajTX9XW5CA==" + } + ] + }, + { + "header": { + "sequence": 9, + "previousBlockHash": "65FD2F3AE1E87C6BF2415006036765003655251969CA5D54617755ACAAE53AB9", + "noteCommitment": { + "type": "Buffer", + "data": "base64:cV6ufQ+Jkm/K1cZvicJCj4urL3MPhHO4NN26WY/KjDo=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:X6+uKCjA/LHtvUFQfArdMV5NFBpPbgKg1D9LZGN3EvA=" + }, + "target": "878277375889837647326843029495509009809390053592540685978895509768758568", + "randomness": "0", + "timestamp": 1675703323226, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 11, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAF/RI+Pc9+D0BFS7GoHEIx+d49OauQu4VZkO9DCuxHYW2dTNOQJlpEfJJzxABYNsuQFktFqKYn170h8omU77+XRI2juQMdeGvFfuDxX0JoRqpUtFtqr++0qXwp+VZvHGPnsW0+sVSzSjYWJj4ZJq97kFT8IL3Z0ECwu5nseRmhRISWQ9217jQXOz6pCeFo4RE7g7JzlFO1jTRTANgo7awgzOBAfrVfzNfy1E9Wh4di92jjPT6BBfaY10El1FMl2FVlvEEMkxtJtTiECze8GtsW1qUry8w7ocSmXH+bgfHoVSq/tkcSeI/3Y84odAd8cFOU+5aR/5WXLx2ax9WoNmKjuDnLRrkWKBfwrHnn1KHkQOPhVdf9PG4xvUKiSxAy24rLgTMoZ3iO8a9376MoiHWyoTAp8MY3E4vArG1gBCgHROSaftE1EWt+NWTprQkv0OIyLcedvhwaMSqc+gvbALDhp5TPuiC5+L00oQ1hYXvpRK73O8D4balr45euk6DIyMo+gsfnNsLHa9VyN+VMAwpdc7jaAtJcf7N75Uofv07rifOkpiU83nlVnM3oOZd7p8boOd8NwhqV1CVndQXG61T91symnIp78x3/3dM8dqNodPqD6WcT4b8gklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOmamcrysgj1yoZ00Kkzt7EoQPvWlzrOsSPRLqTjD7iZQxtsSvCinvJB+V+Awd9AQIMeQJcBTFT8QzZUyYWpIBA==" + } + ] + }, + { + "header": { + "sequence": 10, + "previousBlockHash": "FBF29C669B0870378B9A2D3B871F10DF834DD568D2FBDB170D87875C36822D93", + "noteCommitment": { + "type": "Buffer", + "data": "base64:Uh1IbMBcoyAiQFhBwkcy3OUyb8MBs9vw1mZXZVDGjQE=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:SBzk+HEOgPhTxbiCzcMPs9Nl8zEhT75eAtD1NzwX/IA=" + }, + "target": "875726715553274711274586950997458160797358911132930209640137826778142618", + "randomness": "0", + "timestamp": 1675703323480, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 12, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAVDw+3cvqu2sUz86nATwQ8NydTlhi7oFK0CukgrY+TXOJpyTCfPn+cuaIR7dec6+KKMuMUiX4HabVO+Q0wR1dpCH0DfUh6OZC7aIECDW48Vqj46xSGOLTOe76H6owjFFExZdYXbNyXlpGpGfjOspO/+wq4c9MGW7RyUAlzdF2i4AX5yWzPoYiXoCkMyJp5yPPpNeF+JsCE4/iGefqiUryVCDbXc0mEVpgNsNVBHl+5Kmod2YpjH97cNyQhCyUA2PknMijX0uJOdvp+k+rfh7wr3hZXSn86+v1bOPEARaK8m17nkE5yksAFT56M58C+wvlAI64uHJF9cXxNp7rDa86K1f4MleLnQhqCG4E9TcNNUOJ4zRCRBmBMjP9KC4ZfY0rb+tSiX7ov92J6aWUa9RZZkLHQLqE2D8dh0mrjMivnGfLEfSDIGmZWWhLPeXbh74Ez/VXra0ixkBxcEpZNRdXvn+amHGHy0akRuojXVJ0fqrZV8pFWcWOU/edgeCjPpxZwz9DLb3GoLdNZp++JSBv3wE3339YSHIvsoHuHImrYXrocrwHnpR2gJ0hYS6gBrKLCBISbCKcH0RvDqU1EldsoQaxUm6Og3ss2+DoijOMuPfiQFMR+WDcP0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwvdXHO09cXceV1reNfF5iqGgiklqD2UNRRBgZEluViO603N6j13+b4zZ8mGvc0si24DbzJcr+ZKBb6K8XabZ8DA==" + } + ] + }, + { + "header": { + "sequence": 11, + "previousBlockHash": "B34199B9453526A7A3C7E8EE5E6EF143BA0F02854C2B9A57FC69D394DF4059BE", + "noteCommitment": { + "type": "Buffer", + "data": "base64:pAwsk+oMzf39+RjAT22DfwHHcsEopLjSRUVnWQd0VGU=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:jbknxsKiZyCa4tWRWgnkUHkSOurofM2eCn13ru/XnH4=" + }, + "target": "873190827380823143577845869093025366895436057143163037218399975928398962", + "randomness": "0", + "timestamp": 1675703323735, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 13, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAMdelzP+Yw60Jrcr44YXwib77p/Lh8GJqI5RUC1LnBx+Ci4l6E4/PHTGePyhKhMh9QJ8hjVcL3Agzepw8tbXZqumar2rKoKexaOHMFEkFSCyPPNTJNB1tLUaPnx6uNtBJW1tSRhdG+poEmzMGL+vxK9n/7tcDbqdccorFXrB0TvgHmvEps5u7ETZeeVjvryOLGpynFH46MfE832TwJ1Y8swpRVic30lWNQoBi6Yvh9gCX7y4bcjJg/rE/1AJjB7+xm2NY8Ozwbwn5HqKtZbxTD2fRVT/S5ILYVTIxgIj0YKtP4S6/LlpyDBmuug4tfo2IUbtV1L4BdKKshN6LT2EkAIoyxD5zSrgIUxRtqS43jDVkq8lAvxVOL6tPPHeL37hSn+FQH+Ch/2Smt8F8IgqviRGKO+7b/Dj2MoVg1S4t5IBtKiKYH5ZdhDnUvp0FTZKjBhK77n0AIi2Z5W4RjMcaT6iG6aIv5QshLeuBFCWjuz3aSoSVMerrdAE+LWnYRT91v/ZsbCu00ALxmEc3rkivhqFoIu6LEiNOu/trZXWeT6JpBNSH08n8TH3T+/mcHywL6p4iLfMpdmmoRhKJt8tGD/FvnHwQEkS4GOTZF1ooNrl7KdwCiFWxlklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwuhmC4UOyevsTI6n76Ib4wb5HN/CkAZUJ0NmwrObld+uX62griXgOVVYlY19mh4ZXoJtTUZx9SeTEfXdoAdFoCw==" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/rpc/routes/chain/estimateFeeRates.ts b/ironfish/src/rpc/routes/chain/estimateFeeRates.ts index c4575e5fec..cd11e0ab47 100644 --- a/ironfish/src/rpc/routes/chain/estimateFeeRates.ts +++ b/ironfish/src/rpc/routes/chain/estimateFeeRates.ts @@ -7,9 +7,9 @@ import { ApiNamespace, router } from '../router' export type EstimateFeeRatesRequest = { priority?: PriorityLevel } | undefined export type EstimateFeeRatesResponse = { - low?: string - medium?: string - high?: string + slow?: string + average?: string + fast?: string } export const EstimateFeeRatesRequestSchema: yup.ObjectSchema = yup @@ -20,9 +20,9 @@ export const EstimateFeeRatesRequestSchema: yup.ObjectSchema = yup .object({ - low: yup.string(), - medium: yup.string(), - high: yup.string(), + slow: yup.string(), + average: yup.string(), + fast: yup.string(), }) .defined() @@ -44,9 +44,9 @@ router.register( const feeRates = feeEstimator.estimateFeeRates() request.end({ - low: feeRates.low > 0 ? feeRates.low.toString() : '1', - medium: feeRates.medium > 0 ? feeRates.medium.toString() : '1', - high: feeRates.high > 0 ? feeRates.high.toString() : '1', + slow: feeRates.low > 0 ? feeRates.low.toString() : '1', + average: feeRates.medium > 0 ? feeRates.medium.toString() : '1', + fast: feeRates.high > 0 ? feeRates.high.toString() : '1', }) } }, diff --git a/ironfish/src/rpc/routes/chain/followChain.ts b/ironfish/src/rpc/routes/chain/followChain.ts index 9d99010c73..cd1e22e253 100644 --- a/ironfish/src/rpc/routes/chain/followChain.ts +++ b/ironfish/src/rpc/routes/chain/followChain.ts @@ -37,6 +37,17 @@ export type FollowChainStreamResponse = { fee: number notes: Array<{ commitment: string }> spends: Array<{ nullifier: string }> + mints: Array<{ + id: string + metadata: string + name: string + owner: string + value: string + }> + burns: Array<{ + id: string + value: string + }> }> } } @@ -91,6 +102,29 @@ export const FollowChainStreamResponseSchema: yup.ObjectSchema ({ nullifier: spend.nullifier.toString('hex'), })), + mints: transaction.mints.map((mint) => ({ + id: mint.asset.id().toString('hex'), + metadata: BufferUtils.toHuman(mint.asset.metadata()), + name: BufferUtils.toHuman(mint.asset.name()), + owner: mint.asset.owner().toString('hex'), + value: mint.value.toString(), + })), + burns: transaction.burns.map((burn) => ({ + id: burn.assetId.toString('hex'), + value: burn.value.toString(), + })), } }) }) diff --git a/ironfish/src/rpc/routes/chain/getBlock.ts b/ironfish/src/rpc/routes/chain/getBlock.ts index e60c5f1730..f509011f4c 100644 --- a/ironfish/src/rpc/routes/chain/getBlock.ts +++ b/ironfish/src/rpc/routes/chain/getBlock.ts @@ -2,7 +2,8 @@ * 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 { BlockSerde, GENESIS_BLOCK_SEQUENCE } from '../../../primitives/block' +import { getBlockSize, getTransactionSize } from '../../../network/utils/serializers' +import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives/block' import { BlockHashSerdeInstance } from '../../../serde' import { ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' @@ -165,8 +166,6 @@ router.register( nullifier: BlockHashSerdeInstance.serialize(spend.nullifier), })) - const transactionBuffer = Buffer.from(JSON.stringify(transaction.serialize())) - return { transaction_id: { hash: BlockHashSerdeInstance.serialize(transaction.hash()), @@ -175,15 +174,12 @@ router.register( metadata: { notes, spends, - size: transactionBuffer.byteLength, + size: getTransactionSize(transaction), fee: Number(transaction.fee()), }, } }) - // TODO(IRO-289) We need a better way to either serialize directly to buffer or use CBOR - const blockBuffer = Buffer.from(JSON.stringify(BlockSerde.serialize(block))) - request.end({ blockIdentifier: { index: block.header.sequence.toString(), @@ -196,7 +192,7 @@ router.register( timestamp: block.header.timestamp.getTime(), transactions, metadata: { - size: blockBuffer.byteLength, + size: getBlockSize(block), difficulty: block.header.target.toDifficulty().toString(), }, }) diff --git a/ironfish/src/rpc/routes/chain/getNetworkHashPower.test.ts b/ironfish/src/rpc/routes/chain/getNetworkHashPower.test.ts index f04d163f1a..8fe1b5d24e 100644 --- a/ironfish/src/rpc/routes/chain/getNetworkHashPower.test.ts +++ b/ironfish/src/rpc/routes/chain/getNetworkHashPower.test.ts @@ -30,18 +30,51 @@ describe('Route chain/getNetworkHashPower', () => { expect(response.content).toEqual( expect.objectContaining({ hashesPerSecond: expect.any(Number), + blocks: expect.any(Number), + sequence: expect.any(Number), }), ) }) - it('should fail with a negative lookup value', async () => { + it('should succeed with valid negative sequence value', async () => { + for (let i = 0; i < 5; ++i) { + const block = await useMinerBlockFixture( + routeTest.chain, + undefined, + sender, + routeTest.node.wallet, + ) + + await Promise.all([expect(routeTest.node.chain).toAddBlock(block)]) + await Promise.all([routeTest.node.wallet.updateHead()]) + } + + const offset = -3 + + const response = await routeTest.client.getNetworkHashPower({ + blocks: 100, + sequence: offset, + }) + + const expectedSequence = routeTest.node.chain.head.sequence + offset + + expect(response.content).toEqual( + expect.objectContaining({ + hashesPerSecond: expect.any(Number), + sequence: expectedSequence, + blocks: expectedSequence - 1, + }), + ) + }) + + it('should fail with a negative [blocks] value', async () => { await expect( routeTest.client.getNetworkHashPower({ - lookup: -1, + blocks: -1, }), ).rejects.toThrow( expect.objectContaining({ - message: expect.stringContaining('Lookup value must be greater than 0'), + message: expect.stringContaining('[blocks] value must be greater than 0'), status: 400, code: ERROR_CODES.VALIDATION, }), @@ -50,13 +83,15 @@ describe('Route chain/getNetworkHashPower', () => { it('should return 0 network hash power if start block == end block', async () => { const response = await routeTest.client.getNetworkHashPower({ - lookup: 1, - height: 1, + blocks: 1, + sequence: 1, }) expect(response.content).toEqual( expect.objectContaining({ hashesPerSecond: 0, + blocks: 0, + sequence: 1, }), ) }) diff --git a/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts b/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts index 9deaee023a..c879ee3154 100644 --- a/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts +++ b/ironfish/src/rpc/routes/chain/getNetworkHashPower.ts @@ -7,19 +7,21 @@ import { ValidationError } from '../../adapters' import { ApiNamespace, router } from '../router' export type GetNetworkHashPowerRequest = { - lookup?: number // number of blocks to lookup - height?: number // estimate network speed at the time of the given height + blocks?: number | null // number of blocks to look back + sequence?: number | null // the sequence of the latest block from when to estimate the network speed } export type GetNetworkHashPowerResponse = { hashesPerSecond: number + blocks: number // The actual number of blocks used in the hash rate calculation + sequence: number // The actual sequence of the latest block used in hash rate calculation } export const GetNetworkHashPowerRequestSchema: yup.ObjectSchema = yup .object({ - lookup: yup.number().optional(), - height: yup.number().optional(), + blocks: yup.number().nullable().optional(), + sequence: yup.number().nullable().optional(), }) .defined() @@ -27,6 +29,8 @@ export const GetNetworkHashPowerResponseSchema: yup.ObjectSchema => { - let lookup = request.data?.lookup ?? 120 - const height = request.data?.height ?? -1 - - /* - For bitcoin, a negative lookup specifies using all blocks since the last difficulty change. - For ironfish, the difficulty changes for every block, so this isn't supported. - */ - if (lookup < 0) { - throw new ValidationError('Lookup value must be greater than 0') + let blocks = request.data?.blocks ?? 120 + let sequence = request.data?.sequence ?? -1 + + if (blocks < 0) { + throw new ValidationError('[blocks] value must be greater than 0') } let endBlock = node.chain.head - // estimate network hps at specified height - if (height > 0 && height < node.chain.head.sequence) { - const blockAtHeight = await node.chain.getHeaderAtSequence(height) - if (!blockAtHeight) { - throw new Error(`No end block found at height ${height}`) + // If sequence is negative, it's relative to the head + if (sequence < 0 && Math.abs(sequence) < node.chain.head.sequence) { + sequence = node.chain.head.sequence + sequence + } + + // estimate network hps at specified sequence + // if the sequence is out of bounds, use the head as the last block + if (sequence > 0 && sequence < node.chain.head.sequence) { + const blockAtSequence = await node.chain.getHeaderAtSequence(sequence) + if (!blockAtSequence) { + throw new Error(`No end block found at sequence ${sequence}`) } - endBlock = blockAtHeight + endBlock = blockAtSequence } - // Genesis block has sequence 1 - clamp lookup to prevent going out-of-bounds - if (lookup >= endBlock.sequence) { - lookup = endBlock.sequence - 1 + // Genesis block has sequence 1 - clamp blocks to prevent going out-of-bounds + if (blocks >= endBlock.sequence) { + blocks = endBlock.sequence - 1 } - const startBlock = await node.chain.getHeaderAtSequence(endBlock.sequence - lookup) + const startBlock = await node.chain.getHeaderAtSequence(endBlock.sequence - blocks) if (!startBlock) { - throw new Error(`Failure to find start block ${endBlock.sequence - lookup}`) + throw new Error(`Failure to find start block ${endBlock.sequence - blocks}`) } const startTime = startBlock.timestamp.getTime() @@ -73,6 +79,8 @@ router.register +} + +export const GetBannedPeersRequestSchema: yup.ObjectSchema = yup + .object({ + stream: yup.boolean().optional(), + }) + .optional() + .default({}) + +export const GetBannedPeersResponseSchema: yup.ObjectSchema = yup + .object({ + peers: yup + .array( + yup + .object({ + identity: yup.string().defined(), + reason: yup.string().defined(), + }) + .defined(), + ) + .defined(), + }) + .defined() + +router.register( + `${ApiNamespace.peer}/getBannedPeers`, + GetBannedPeersRequestSchema, + (request, node): void => { + const peerNetwork = node.peerNetwork + + const peers = getPeers(peerNetwork) + + if (!request.data?.stream) { + request.end({ peers }) + return + } + + request.stream({ peers }) + + const interval = setInterval(() => { + const peers = getPeers(peerNetwork) + request.stream({ peers }) + }, 1000) + + request.onClose.on(() => { + clearInterval(interval) + }) + }, +) + +function getPeers(network: PeerNetwork): BannedPeerResponse[] { + return [...network.peerManager.banned.entries()].map(([identity, reason]) => { + return { identity, reason } + }) +} diff --git a/ironfish/src/rpc/routes/peers/index.ts b/ironfish/src/rpc/routes/peers/index.ts index b638f8eaeb..74050288fc 100644 --- a/ironfish/src/rpc/routes/peers/index.ts +++ b/ironfish/src/rpc/routes/peers/index.ts @@ -4,4 +4,5 @@ export * from './getPeers' export * from './getPeer' +export * from './getBannedPeers' export * from './getPeerMessages' diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture index b17f8c746a..e62b0fc240 100644 --- a/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/createTransaction.test.ts.fixture @@ -185,7 +185,7 @@ ] } ], - "Route wallet/createTransaction should generate a valid transaction with multiple receives": [ + "Route wallet/createTransaction should generate a valid transaction with multiple outputs": [ { "header": { "sequence": 5, diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/getAssets.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/getAssets.test.ts.fixture new file mode 100644 index 0000000000..3e81eed464 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/getAssets.test.ts.fixture @@ -0,0 +1,76 @@ +{ + "wallet/getAssets returns a stream of assets the wallet owns": [ + { + "id": "c5e4126e-8c62-4eda-9806-6bbcce83e48d", + "name": "accountA", + "spendingKey": "1321db3cf483e373cad841f81a753b5c213c58dbca752719c50dd0114b755664", + "incomingViewKey": "ad49faeb5dd2085656d0ec84a7e875717272201986b416c7a2b38890800d1001", + "outgoingViewKey": "1b3474be164c9360a4afb1e0732c372afb559c359e59eacdd30b57593db8c932", + "publicAddress": "4c8836655eb66522af23eb4a52471b0a3c841d836effb5c66c2ae7ae7fc02f95" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:3uqY4ZzWDBwjjn04usH6bfiNpP5JmXfy8V1sKichRj4=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:55ICy82zPB8gsMXk7ISdHHqhUc/emLyxanViZthXzXs=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675708572152, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAcvSK7TTBEwDR2ccS6KgpVzaEAOWE2BCrjLE84qz0LCC1c3rhljz5qdKfId2RYrjKyY6sVELVqYKetpaSGp3N1XYsu3aa1qQi27GkvgDndoOPXHdX2WxTNvBFtYOxNzCRHBsimJBClxbZQtU4ACIlXcAf6WA9BCI4LmfaXdIe7PgCz0ub58Fvs9Kf+HnBE7+DBic2/MqhZ2WnrIe9w8isGDTHcRyPqaQfxUW/ef5/Nl+gVRuKyhJkELQGVTk46lpmz4X30OVV/kq+ZW7AvdAY4nxLWYhi5spQtcF9nRhgn5b7NVKu/n9hGJIsVOcfy+zH5NFbdTFcTm0Q+4HynbFTQtqWxobOPq4uqe0YZ7epQy35SvWDIYUBAYibvbQBTWoGpHL4tFqh/PXe/H7gw8AxmE998IUTNok7mdOerAE9GCJ2VUv+yd8/4cins+ZGERlinyH46cYl1EBQfWONu3UkbLA88NXfpbjGnJc4qxNDf58Bw/dPUnCsHZyuzWI128qE5E4xHxa5JC/GCDdw9/otHEmw6ODXWnVBzY1lJy0V+TJRRK6aIr1VHSkN3lVr8xEtNPyirHxlc3wfM8A1jxEVWB/vRMF46jh2kFjCdt3h/gcp/45F4FeaHElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAweyYPpNkjILIMXkAjr519/ZJdX7juhMfBg3oXp1b2m1WG5YjEKIGu4AAtrEpwJf1RRz3sym+B5391FCOxhO5kAw==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+6VP+n0OcTMzCf5dBQF6CG8vIW+KCM11D1K1dvaPYSK2RgTb2PKy1aPWpdTYQoaEUg+DscX/GIO9NtaxMiSotjiGxWU1Dguns1uJb+qHfYGK1bsblwcUBXgTdx+So/XABSit5jm307n/Z6NI5B6Tda8nsRF1ZdfTW7DDPRP2nmAVr/vQyM/sUom2Ib/SACv37bXjpD0+3d6ixTs0JWhF7LkjrjfW264Yhf/rN3I5cH+jKFiqN5y9pZsr0ypipNrZ+k9n24gCs07/ZGQtYa7MOcSfPZO2rWTwdLAmJ/EHPRDFTe8lHB0O+/mKFp796i09M3k2Se7l1C+M8zFec+6LJi+oBhzNdJebRWCM2ctQCO9NAFX5VF5wVKrN8fC8dB9uMPKD5I7z4HzF4RGHgin93wG/0HH9RAMaV7+dIV8PX68JUuoPEqG3SPaul874i+D8Pde7nWQ7lK1+ezZdjKtTpgCRYnind3AZb/c6/vNfus5lwgKwTBKzeXf0o3k9M6mOha05QoDiNbBAlRFe9sncEQFJUFpIl2DwHHCsvXU0EC63yMeH1a5YvdqbgkbQd9mmARnHnJRAlN+wtndaGAV8uzQdK4CDkxE16YyZ0g+TJh3StGWK97iB+D2mt1UN+5g3ZLXVFc2B9rXbW8xq+0PVzjP7YRjsva9zXEpNe7JIDjHPlSL0N8Zpob9hEAMMhtA7IaRdv9yl570iPtJFtS/ykvRoVaKqvUwAh6olrPGWbsPIdTlZMmHCmhnSdFbYEqGlZYfs1g5opFF7ifd4g/azgfPWw7DnrrhdjQtc86SKDUB+2xVvJjNDf2O8Anfn7r9g+/uouK2dzqFsPnSR5I8nYmwc3HSbwec/BDPZcmtF0ngfXC1iu62xRK7g1OZB5/rQQW/c3eg2IUWOnx2g0iWFfFAPwzXrXu7oolg6ALzelddnsQIkhrPb6UTnU9c6IkRwTGUXutD8tPMune9wcCE2t01bO5+Lcmh3TIg2ZV62ZSKvI+tKUkcbCjyEHYNu/7XGbCrnrn/AL5VhY2NvdW50LWFzc2V0AAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAABbybyCucZbslZBpva9rKyPgFsQeiA4j4PdLjQKYn1eWAUzWehADAXFAZLFr22fWKChVR4vd6788JjqznRJT6MAHJR0TV6yY2jerfabHitHLse4TawCqVP0ZS3qYmuqQPNXdj6RG2QeTgmV7ma4fYUx0EWJL7FwICIN31YHdVKCDQ==" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "D071175E5AA709B376231A9C6FC1D785780A58C7AA920D133D6742DFA5B96D26", + "noteCommitment": { + "type": "Buffer", + "data": "base64:XOUADVHSAOB9bVueLuI7+j8VkDQzA+ZC4HCULcIbrSk=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:+06ntyNzsmsdarMldDeHAt/xi2MOjD8iJaX1OF9Se9M=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675708573442, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 6, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAksJLs6gY1KEQjKhrU7mpCSHprGLMv8PkIi4p1/7L99KK+urCqlKMfc4iDfmqw5P9SfJt1wNzlRC98Nqy58OV/pj64D5oP1knMYo5GshZUvOtVm2onKnFCfIDwZ+yLx83FZ/lZFiLV7M9CGut5YKP4Sn9o+dZmjQ96wZAn07AGxAQpQrv7zV+TvBzDo6uYkD9eiu6yx9F2xMqT0prMFHPpEuw0sZOfk6w7pQlFnSHl2ysmGvaqfRcQ6hOuSQeWlKQqnxQGl3utzUNnJWnIRnnVovaHM8spbfNbNrq13qtJa4n2WjJu5L/c1s4oKLMfhkFhlRO0w005cT6O1hh3Ig7DjNJ+KMsxUHpXxa12Ytg5eyUp6uj3FjMmFYrC2dT6UdK1Rh6rP2H10uRQNn1J5SiTTNmN6R0LwtvY375xXw08dcfmAvhh7bPV7sMFlt1RgXr0YYnt/aPdNNyDW1fNclvmEOWhFhOBzi+eNxxKopNa70svnWkGTBvsLE45ZIXIxqMBfvReRoSJXqQq6BTsUDIUaq8XALQlE36NXCcJ21kUukkutV8h/KG04YPWKxHFY5xErJ9TIrz6RptxKWpVSdFji7RsBtX/Y0XTBR+bJr6LBZQ1jTAtaQMnUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNEgu7O03HK4QktooY7Ilv5JeYTx84iglVtxCYt60GVFfEAavtMnK597OuoEJUUL2F4oJg/uTmWRHiwsphtNPCA==" + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+6VP+n0OcTMzCf5dBQF6CG8vIW+KCM11D1K1dvaPYSK2RgTb2PKy1aPWpdTYQoaEUg+DscX/GIO9NtaxMiSotjiGxWU1Dguns1uJb+qHfYGK1bsblwcUBXgTdx+So/XABSit5jm307n/Z6NI5B6Tda8nsRF1ZdfTW7DDPRP2nmAVr/vQyM/sUom2Ib/SACv37bXjpD0+3d6ixTs0JWhF7LkjrjfW264Yhf/rN3I5cH+jKFiqN5y9pZsr0ypipNrZ+k9n24gCs07/ZGQtYa7MOcSfPZO2rWTwdLAmJ/EHPRDFTe8lHB0O+/mKFp796i09M3k2Se7l1C+M8zFec+6LJi+oBhzNdJebRWCM2ctQCO9NAFX5VF5wVKrN8fC8dB9uMPKD5I7z4HzF4RGHgin93wG/0HH9RAMaV7+dIV8PX68JUuoPEqG3SPaul874i+D8Pde7nWQ7lK1+ezZdjKtTpgCRYnind3AZb/c6/vNfus5lwgKwTBKzeXf0o3k9M6mOha05QoDiNbBAlRFe9sncEQFJUFpIl2DwHHCsvXU0EC63yMeH1a5YvdqbgkbQd9mmARnHnJRAlN+wtndaGAV8uzQdK4CDkxE16YyZ0g+TJh3StGWK97iB+D2mt1UN+5g3ZLXVFc2B9rXbW8xq+0PVzjP7YRjsva9zXEpNe7JIDjHPlSL0N8Zpob9hEAMMhtA7IaRdv9yl570iPtJFtS/ykvRoVaKqvUwAh6olrPGWbsPIdTlZMmHCmhnSdFbYEqGlZYfs1g5opFF7ifd4g/azgfPWw7DnrrhdjQtc86SKDUB+2xVvJjNDf2O8Anfn7r9g+/uouK2dzqFsPnSR5I8nYmwc3HSbwec/BDPZcmtF0ngfXC1iu62xRK7g1OZB5/rQQW/c3eg2IUWOnx2g0iWFfFAPwzXrXu7oolg6ALzelddnsQIkhrPb6UTnU9c6IkRwTGUXutD8tPMune9wcCE2t01bO5+Lcmh3TIg2ZV62ZSKvI+tKUkcbCjyEHYNu/7XGbCrnrn/AL5VhY2NvdW50LWFzc2V0AAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAABbybyCucZbslZBpva9rKyPgFsQeiA4j4PdLjQKYn1eWAUzWehADAXFAZLFr22fWKChVR4vd6788JjqznRJT6MAHJR0TV6yY2jerfabHitHLse4TawCqVP0ZS3qYmuqQPNXdj6RG2QeTgmV7ma4fYUx0EWJL7FwICIN31YHdVKCDQ==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] +} \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/mintAsset.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/mintAsset.test.ts.fixture index 20e3758dbb..5dd5e23ce4 100644 --- a/ironfish/src/rpc/routes/wallet/__fixtures__/mintAsset.test.ts.fixture +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/mintAsset.test.ts.fixture @@ -1,16 +1,16 @@ { "mint with valid parameters returns the asset identifier and transaction hash": [ { - "id": "be82627b-d4da-4fae-bff9-4fc2970f77e3", + "id": "fc0c3676-ca10-4dc3-8af6-7d091242b88a", "name": "test", - "spendingKey": "935e7598bf0e1d84c5b367549d19d1c2032faef93477de4e91758cfbf261813d", - "incomingViewKey": "d6e951dd5c0e15bb46892ec204175e173b889a751b4be7a96f5b1b2bf4267b04", - "outgoingViewKey": "f35aaf0e5a29bad9391a6620083a1fbbd33a0394f81bb6a5be898106b65ba826", - "publicAddress": "3618a9cfad256dcca238fba310a17e7b37c3b637b368e470232e587e14402b93" + "spendingKey": "c5d63a869d87b784c43e0168c8d9e0337711d66b959402b3a699e3a4acbff621", + "incomingViewKey": "eb845d2ba3881f7cd1dae96b3294b4105b8884eb30e989e4197f6f291fb3b007", + "outgoingViewKey": "11815decc2a17292e55f41e9e26a829cb58583d8e75a34c5e16c5fd1c579e0c7", + "publicAddress": "ebd73672754462cad15590c65daa611e15804adca88f7d8d3a8ea79021f8c949" }, { "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp/Ub6okk91jDmiW/OG3rHb5tMSirLqe8OQgVBdB7KlW1YWP/KVb8H6us0QLIvXVLCmaq601UbNxZfCA7MMT3Sad543Jga5CopaaHeyY/iy6GFuNPthSyXe3hFEIZPQTHUZmSSFDjCC86g/i2R1JrIPgcLYrTGG/PPXQSOITvuaQU3wlPF4NyJUSNpSpeIzKl7bQbO6bKnw/UwPs+BrftGeU7z+oS6ADyQPrmPBL6d0GMc6vvcjAngZCb12iUWrbZdBsPQDPG2Eo0aTghxkUFITda1gKzqTTrHf8UmcssajUffLFrmkw2r+uu/mbN8hq/BWeyeKoZMgAn9lISv9rH3cqs4gGiVV6c4xrnBsvCnBLIjqbU0to1h1AZxw1B7F9JFWRgYaykeJ3ZBWKIipQKV3vaD4Eqe2nF9dTdn+Uj0ZAZyogVWnWz4zCdiBvPZAK804eXy2JPd6/iOcGXLCX7WrVA2gD4eoe8s8N6q38fznUONY5kyfY+gZKJCPOVjm7dcQD+dTv8BjPCXDYZ7GCXkCeYeQdCycEZ3J5Y5lI3vR+j+3xQvsuu7ca+BV5781g+DkEZpIGJNNe80H/4DbAsl7S/58Cuv9ZAsx6qT7pYkY8fAiMJKEtJAYbecb/6Ye+1H1kylgQzEYhT8+MKlE3kD5CpkUKyLmQe2cJ8IFdaU1eF4MpNGXt033xP9I0Zp5Rtd7+BzB5IWdjsF8c4vHdqpQgT/etDlkUMtQpRNBbHKhanjYpxCqz/KJKgoOAEI9UOmlH7N9tBJNdt1IuuHaraC+NXim8DIarUqeWatXnqrGKsHrL4FGtnJ689bBHiGbO8WGdCxXguBfnO2cd6pMdZS1R9VeXAmLGxAYXF8tZFSzx1dXze8ubRreUrvjqUyAVhYbGxZuJIh8CKts9pCWoKPI0oi2y2Zbogt9B02KTIqtkoUfdZhvmVdEv/X67ipsSRwVi3V2g/jy83NPW0O3NLHvWXSWVDXinrNhipz60lbcyiOPujEKF+ezfDtjezaORwIy5YfhRAK5NtaW50LWFzc2V0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAhKsCA2rtb+wzDdNkBs+viDA081XT2JaEtHN46sOuehU21zvaNLoiHxu5lDQeb1fqaO3WOtXwdNQw2T0gWcoAMYdRRNIvZGTwXPLG3Ik4rqwndR10M1arrFRu1zBujh1PLDnZ2ucuK3/QzuiaO0Jk2y6bJDl/ENy6xYr5fm9kRAw==" + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvuILP4ppyiX6Immg6HvVkgYQyWlr9wzRoj/X3kOxBiiHIbADMTteN6em6InyZZnPMq679B6qAUSo7kls/QtICxM9Fdjb51BRqxTWH54HPv6Tb7cvrbgMdfedlQW68jbRaeS5wdkbe0eepWJHVGvKVKgCTvprcLEPYwmL/HlanUEUqIsKFngDx2zg2+B4Ak0Gi2nUZok0G/N2zWGX6x6xkvL0PO8h9FHCIQveSIYdIhejK84Fg5n8GjdEu7skkpZe+2OeuJRmUrp4Po5qk993pSGaNU+UngGbfFfTUB1Yj2hPOfsJqpAehJ+mNdDuePl+XH0d5+qLPzVdpcGcYYgbmyYP2+RoPmC25HacDZnqui19oqg+8CsKyrvzX4K4sQxo69AZslW8JOLBPfC2lsD7HwsFYU8L6hj9JXnAS8UCY3GhfcrkvMy0RQtInhsWTv4/i86l/z7SMQ488FPfRyP9tPgXLDtck6kt/7y14YxA9NSorFcXj57pOmWXpJXKBOApTIqNoism+Uv/ZF2nO2keSLyzN0aObZR9hkp/VHPX6oz7f6vvtJhtiVR0tS80t44fpRQTUDFZNttX+91CNWoUtWgEi3xe0UyeRRBI9hy4uXepbBMPWP32O4GNH4LxX5wEPuC1bPcj2OiPsszfro6Tu0LQMZ4++7Q87gq5BBvqopsxQH4jd1vClAUWhseQipEII0oiWoaOrcZFAHwxIMjE5q+GzgMIk+f3mKSFpy6IZEh0VYLEMUw+v4BAMKd2dMVT/n/Ya/Vap+V2TYl0D/D8ZoG1LaVGbNUduG9pIbF0o/ssyrNO6p3hOr7oIpaRLDIYUPXSCxtst2BZF5Y0Auigd1ILS+6zRPByBn0MJB0XGc4FBeqbfjASW2d9iB+WrwL/OCz3NdPx/cXf5PgCfV5MNtQKeVC5zGmDjR6/XF4JFQJQL5ihThwBPSEjxIaFMxyE2xhBp+Jrc+g7y+G6vmhuXAToX75wDtzm69c2cnVEYsrRVZDGXaphHhWAStyoj32NOo6nkCH4yUltaW50LWFzc2V0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAABNib4p9LK4ZeJplV/ilpXYFwLgfbPq6nRU7S6x0KY22tD7n28C2rC8uCQKv48zNgEdhyY2ZzeEAbXQW7qqcFEJv+2j2xgXhbbEAYdqZkuorskYunttuw5KCa7Phap89eRk2S4sAE889YzUQv3/8IBzIEeU93kCJrUfEEJ5q9/SBA==" } ] } \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.test.ts b/ironfish/src/rpc/routes/wallet/burnAsset.test.ts index f4af340606..c754f3d7a5 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.test.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.test.ts @@ -87,6 +87,7 @@ describe('burnAsset', () => { expect(response.content).toEqual({ assetId: asset.id().toString('hex'), + name: asset.name().toString('hex'), hash: burnTransaction.hash().toString('hex'), value: burnTransaction.burns[0].value.toString(), }) diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.ts b/ironfish/src/rpc/routes/wallet/burnAsset.ts index f4dd0a2ada..54cc23e660 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.ts @@ -20,6 +20,7 @@ export interface BurnAssetRequest { export interface BurnAssetResponse { assetId: string hash: string + name: string value: string } @@ -39,6 +40,7 @@ export const BurnAssetResponseSchema: yup.ObjectSchema = yup .object({ assetId: yup.string().required(), hash: yup.string().required(), + name: yup.string().required(), value: yup.string().required(), }) .defined() @@ -62,10 +64,14 @@ router.register( throw new ValidationError('Invalid burn amount') } + const assetId = Buffer.from(request.data.assetId, 'hex') + const asset = await node.chain.getAssetById(assetId) + Assert.isNotNull(asset) + const transaction = await node.wallet.burn( node.memPool, account, - Buffer.from(request.data.assetId, 'hex'), + assetId, value, fee, request.data.expirationDelta ?? node.config.get('transactionExpirationDelta'), @@ -78,6 +84,7 @@ router.register( request.end({ assetId: burn.assetId.toString('hex'), hash: transaction.hash().toString('hex'), + name: asset.name.toString('hex'), value: CurrencyUtils.encode(burn.value), }) }, diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.test.slow.ts b/ironfish/src/rpc/routes/wallet/createTransaction.test.slow.ts index 2f8ec2a59c..c7c33e2831 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.test.slow.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.test.slow.ts @@ -57,7 +57,7 @@ describe('Route wallet/createTransaction', () => { const response = await routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -80,7 +80,7 @@ describe('Route wallet/createTransaction', () => { const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) - expect(rawTransaction.receives.length).toBe(1) + expect(rawTransaction.outputs.length).toBe(1) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(1) diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.test.ts b/ironfish/src/rpc/routes/wallet/createTransaction.test.ts index 49fcae0971..e6948af928 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.test.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.test.ts @@ -11,7 +11,7 @@ import { ERROR_CODES } from '../../adapters/errors' const REQUEST_PARAMS = { sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -24,7 +24,7 @@ const REQUEST_PARAMS = { const REQUEST_PARAMS_WITH_MULTIPLE_RECIPIENTS = { sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -107,7 +107,7 @@ describe('Route wallet/createTransaction', () => { const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) - expect(rawTransaction.receives.length).toBe(1) + expect(rawTransaction.outputs.length).toBe(1) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(0) @@ -115,7 +115,7 @@ describe('Route wallet/createTransaction', () => { expect(rawTransaction.fee).toBe(1n) }) - it('should generate a valid transaction with multiple receives', async () => { + it('should generate a valid transaction with multiple outputs', async () => { routeTest.node.peerNetwork['_isReady'] = true routeTest.chain.synced = true @@ -142,7 +142,7 @@ describe('Route wallet/createTransaction', () => { const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) - expect(rawTransaction.receives.length).toBe(2) + expect(rawTransaction.outputs.length).toBe(2) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(0) @@ -169,7 +169,7 @@ describe('Route wallet/createTransaction', () => { const response = await routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -187,7 +187,7 @@ describe('Route wallet/createTransaction', () => { const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) - expect(rawTransaction.receives.length).toBe(1) + expect(rawTransaction.outputs.length).toBe(1) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(0) @@ -214,7 +214,7 @@ describe('Route wallet/createTransaction', () => { const response = await routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -231,7 +231,7 @@ describe('Route wallet/createTransaction', () => { const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) - expect(rawTransaction.receives.length).toBe(1) + expect(rawTransaction.outputs.length).toBe(1) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(0) @@ -260,7 +260,7 @@ describe('Route wallet/createTransaction', () => { const response = await routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -285,7 +285,7 @@ describe('Route wallet/createTransaction', () => { const rawTransactionBytes = Buffer.from(response.content.transaction, 'hex') const rawTransaction = RawTransactionSerde.deserialize(rawTransactionBytes) - expect(rawTransaction.receives.length).toBe(1) + expect(rawTransaction.outputs.length).toBe(1) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(1) @@ -315,7 +315,7 @@ describe('Route wallet/createTransaction', () => { await expect( routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -356,7 +356,7 @@ describe('Route wallet/createTransaction', () => { await expect( routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), @@ -393,7 +393,7 @@ describe('Route wallet/createTransaction', () => { await expect( routeTest.client.createTransaction({ sender: 'existingAccount', - receives: [ + outputs: [ { publicAddress: '0d804ea639b2547d1cd612682bf99f7cad7aad6d59fd5457f61272defcd4bf5b', amount: BigInt(10).toString(), diff --git a/ironfish/src/rpc/routes/wallet/createTransaction.ts b/ironfish/src/rpc/routes/wallet/createTransaction.ts index 07ef891fec..d4bbb11c21 100644 --- a/ironfish/src/rpc/routes/wallet/createTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/createTransaction.ts @@ -13,7 +13,7 @@ import { ApiNamespace, router } from '../router' export type CreateTransactionRequest = { sender: string - receives: { + outputs: { publicAddress: string amount: string memo: string @@ -43,7 +43,7 @@ export type CreateTransactionResponse = { export const CreateTransactionRequestSchema: yup.ObjectSchema = yup .object({ sender: yup.string().defined(), - receives: yup + outputs: yup .array( yup .object({ @@ -126,13 +126,13 @@ router.register { + const outputs = data.outputs.map((output) => { let assetId = Asset.nativeId() - if (receive.assetId) { - assetId = Buffer.from(receive.assetId, 'hex') + if (output.assetId) { + assetId = Buffer.from(output.assetId, 'hex') } - const amount = CurrencyUtils.decode(receive.amount) + const amount = CurrencyUtils.decode(output.amount) if (amount <= 0) { throw new ValidationError(`Invalid transaction amount ${amount}.`) } @@ -141,9 +141,9 @@ router.register( ExportAccountRequestSchema, (request, node): void => { const account = getAccount(node, request.data.account) - request.end({ account: account.serialize() }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...accountInfo } = account.serialize() + request.end({ account: accountInfo }) }, ) diff --git a/ironfish/src/rpc/routes/wallet/getAssets.test.ts b/ironfish/src/rpc/routes/wallet/getAssets.test.ts new file mode 100644 index 0000000000..10815894a6 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/getAssets.test.ts @@ -0,0 +1,71 @@ +/* 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 '../../../testUtilities/matchers' +import { Asset } from '@ironfish/rust-nodejs' +import { + useAccountFixture, + useMinerBlockFixture, + useMintBlockFixture, + usePostTxFixture, +} from '../../../testUtilities' +import { createRouteTest } from '../../../testUtilities/routeTest' +import { AsyncUtils } from '../../../utils' +import { AssetStatus } from '../../../wallet' + +describe('wallet/getAssets', () => { + const routeTest = createRouteTest() + + it('returns a stream of assets the wallet owns', async () => { + const node = routeTest.node + const account = await useAccountFixture(node.wallet, 'accountA') + + const minerBlock = await useMinerBlockFixture(node.chain, undefined, account) + await expect(node.chain).toAddBlock(minerBlock) + + const asset = new Asset(account.spendingKey, 'account-asset', 'metadata') + const value = BigInt(10) + const mintBlock = await useMintBlockFixture({ node, account, asset, value }) + await expect(node.chain).toAddBlock(mintBlock) + await node.wallet.updateHead() + + const pendingAsset = new Asset(account.spendingKey, 'pending', 'metadata') + const pendingMint = await usePostTxFixture({ + node, + wallet: node.wallet, + from: account, + mints: [ + { + name: pendingAsset.name().toString('hex'), + metadata: pendingAsset.metadata().toString('hex'), + value, + }, + ], + }) + + const response = routeTest.client.getAssets({ account: account.name }) + + const assets = await AsyncUtils.materialize(response.contentStream()) + expect(assets).toEqual([ + { + createdTransactionHash: pendingMint.hash().toString('hex'), + id: pendingAsset.id().toString('hex'), + metadata: pendingAsset.metadata().toString('hex'), + name: pendingAsset.name().toString('hex'), + owner: account.publicAddress, + status: AssetStatus.PENDING, + supply: undefined, + }, + { + createdTransactionHash: mintBlock.transactions[1].hash().toString('hex'), + id: asset.id().toString('hex'), + metadata: asset.metadata().toString('hex'), + name: asset.name().toString('hex'), + owner: account.publicAddress, + status: AssetStatus.CONFIRMED, + supply: value.toString(), + }, + ]) + }) +}) diff --git a/ironfish/src/rpc/routes/wallet/getAssets.ts b/ironfish/src/rpc/routes/wallet/getAssets.ts new file mode 100644 index 0000000000..6738991405 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/getAssets.ts @@ -0,0 +1,70 @@ +/* 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 * as yup from 'yup' +import { CurrencyUtils } from '../../../utils' +import { ApiNamespace, router } from '../router' +import { getAccount } from './utils' + +export type GetAssetsRequest = { + account?: string + confirmations?: number +} + +export type GetAssetsResponse = { + createdTransactionHash: string + id: string + metadata: string + name: string + owner: string + status: string + supply?: string +} + +export const GetAssetsRequestSchema: yup.ObjectSchema = yup + .object() + .shape({ + account: yup.string(), + confirmations: yup.number().optional(), + }) + .defined() + +export const GetAssetsResponseSchema: yup.ObjectSchema = yup + .object({ + createdTransactionHash: yup.string().defined(), + id: yup.string().defined(), + metadata: yup.string().defined(), + name: yup.string().defined(), + owner: yup.string().defined(), + status: yup.string().defined(), + supply: yup.string().optional(), + }) + .defined() + +router.register( + `${ApiNamespace.wallet}/getAssets`, + GetAssetsRequestSchema, + async (request, node): Promise => { + const account = getAccount(node, request.data.account) + + for await (const asset of account.getAssets()) { + if (request.closed) { + break + } + + request.stream({ + createdTransactionHash: asset.createdTransactionHash.toString('hex'), + id: asset.id.toString('hex'), + metadata: asset.metadata.toString('hex'), + name: asset.name.toString('hex'), + owner: asset.owner.toString('hex'), + status: await node.wallet.getAssetStatus(account, asset, { + confirmations: request.data.confirmations, + }), + supply: asset.supply ? CurrencyUtils.encode(asset.supply) : undefined, + }) + } + + request.end() + }, +) diff --git a/ironfish/src/rpc/routes/wallet/index.ts b/ironfish/src/rpc/routes/wallet/index.ts index 2d7126d4b6..99f648b0d6 100644 --- a/ironfish/src/rpc/routes/wallet/index.ts +++ b/ironfish/src/rpc/routes/wallet/index.ts @@ -7,6 +7,7 @@ export * from './burnAsset' export * from './create' export * from './exportAccount' export * from './getAccounts' +export * from './getAssets' export * from './getBalance' export * from './getBalances' export * from './getDefaultAccount' diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.test.ts b/ironfish/src/rpc/routes/wallet/mintAsset.test.ts index 3cadaee666..935d4a7554 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.test.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.test.ts @@ -90,7 +90,7 @@ describe('mint', () => { expect(response.content).toEqual({ assetId: asset.id().toString('hex'), hash: mintTransaction.hash().toString('hex'), - name: asset.name().toString('utf8'), + name: asset.name().toString('hex'), value: mintTransaction.mints[0].value.toString(), }) }) diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.ts b/ironfish/src/rpc/routes/wallet/mintAsset.ts index 5c286aece4..b385df1f97 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.ts @@ -105,7 +105,7 @@ router.register( request.end({ assetId: mint.asset.id().toString('hex'), hash: transaction.hash().toString('hex'), - name: mint.asset.name().toString('utf8'), + name: mint.asset.name().toString('hex'), value: mint.value.toString(), }) }, diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts index a018b17783..00da790e34 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.test.ts @@ -10,7 +10,7 @@ import { ERROR_CODES } from '../../adapters' const TEST_PARAMS = { fromAccountName: 'existingAccount', - receives: [ + outputs: [ { publicAddress: 'test2', amount: BigInt(10).toString(), @@ -23,7 +23,7 @@ const TEST_PARAMS = { const TEST_PARAMS_MULTI = { fromAccountName: 'existingAccount', - receives: [ + outputs: [ { publicAddress: 'test2', amount: BigInt(10).toString(), diff --git a/ironfish/src/rpc/routes/wallet/sendTransaction.ts b/ironfish/src/rpc/routes/wallet/sendTransaction.ts index fb30f61831..dd610b6c22 100644 --- a/ironfish/src/rpc/routes/wallet/sendTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/sendTransaction.ts @@ -11,7 +11,7 @@ import { ApiNamespace, router } from '../router' export type SendTransactionRequest = { fromAccountName: string - receives: { + outputs: { publicAddress: string amount: string memo: string @@ -24,7 +24,7 @@ export type SendTransactionRequest = { } export type SendTransactionResponse = { - receives: { + outputs: { publicAddress: string amount: string memo: string @@ -37,7 +37,7 @@ export type SendTransactionResponse = { export const SendTransactionRequestSchema: yup.ObjectSchema = yup .object({ fromAccountName: yup.string().defined(), - receives: yup + outputs: yup .array( yup .object({ @@ -58,7 +58,7 @@ export const SendTransactionRequestSchema: yup.ObjectSchema = yup .object({ - receives: yup + outputs: yup .array( yup .object({ @@ -100,16 +100,16 @@ router.register( ) } - const receives = transaction.receives.map((receive) => { + const outputs = transaction.outputs.map((output) => { let assetId = Asset.nativeId() - if (receive.assetId) { - assetId = Buffer.from(receive.assetId, 'hex') + if (output.assetId) { + assetId = Buffer.from(output.assetId, 'hex') } return { - publicAddress: receive.publicAddress, - amount: CurrencyUtils.decode(receive.amount), - memo: receive.memo, + publicAddress: output.publicAddress, + amount: CurrencyUtils.decode(output.amount), + memo: output.memo, assetId, } }) @@ -121,7 +121,7 @@ router.register( const totalByAssetIdentifier = new BufferMap() totalByAssetIdentifier.set(Asset.nativeId(), fee) - for (const { assetId, amount } of receives) { + for (const { assetId, amount } of outputs) { if (amount < 0) { throw new ValidationError(`Invalid transaction amount ${amount}.`) } @@ -147,7 +147,7 @@ router.register( const transactionPosted = await node.wallet.send( node.memPool, account, - receives, + outputs, BigInt(transaction.fee), transaction.expirationDelta ?? node.config.get('transactionExpirationDelta'), transaction.expiration, @@ -155,7 +155,7 @@ router.register( ) request.end({ - receives: transaction.receives, + outputs: transaction.outputs, fromAccountName: account.name, hash: transactionPosted.hash().toString('hex'), }) diff --git a/ironfish/src/strategy.test.slow.ts b/ironfish/src/strategy.test.slow.ts index 4d2bbfed55..16e58b47f9 100644 --- a/ironfish/src/strategy.test.slow.ts +++ b/ironfish/src/strategy.test.slow.ts @@ -98,7 +98,7 @@ describe('Demonstrate the Sapling API', () => { minerNote = new NativeNote(owner, BigInt(42), '', Asset.nativeId(), owner) const transaction = new NativeTransaction(spenderKey.spending_key) - transaction.receive(minerNote) + transaction.output(minerNote) minerTransaction = new NativeTransactionPosted(transaction.post_miners_fee()) expect(minerTransaction).toBeTruthy() expect(minerTransaction.notesLength()).toEqual(1) @@ -143,7 +143,7 @@ describe('Demonstrate the Sapling API', () => { Asset.nativeId(), minerNote.owner(), ) - transaction.receive(outputNote) + transaction.output(outputNote) publicTransaction = new NativeTransactionPosted(transaction.post(null, BigInt(0))) expect(publicTransaction).toBeTruthy() @@ -296,8 +296,8 @@ describe('Demonstrate the Sapling API', () => { receiverAddress, ) - transaction.receive(noteForSpender) - transaction.receive(receiverNoteToSelf) + transaction.output(noteForSpender) + transaction.output(receiverNoteToSelf) const postedTransaction = new NativeTransactionPosted( transaction.post(undefined, BigInt(1)), diff --git a/ironfish/src/testUtilities/fixtures/blocks.ts b/ironfish/src/testUtilities/fixtures/blocks.ts index f69fa584ca..9d550ea6b0 100644 --- a/ironfish/src/testUtilities/fixtures/blocks.ts +++ b/ironfish/src/testUtilities/fixtures/blocks.ts @@ -147,7 +147,7 @@ export async function useBlockWithRawTxFixture( pool: WorkerPool, sender: Account, notesToSpend: NoteEncrypted[], - receives: { publicAddress: string; amount: bigint; memo: string; assetId: Buffer }[], + outputs: { publicAddress: string; amount: bigint; memo: string; assetId: Buffer }[], mints: MintData[], burns: BurnDescription[], sequence: number, @@ -176,16 +176,16 @@ export async function useBlockWithRawTxFixture( raw.fee = BigInt(0) raw.spends = spends - for (const receive of receives) { + for (const output of outputs) { const note = new NativeNote( - receive.publicAddress, - receive.amount, - receive.memo, - receive.assetId, + output.publicAddress, + output.amount, + output.memo, + output.assetId, sender.publicAddress, ) - raw.receives.push({ note: new Note(note.serialize()) }) + raw.outputs.push({ note: new Note(note.serialize()) }) } const transaction = await pool.postTransaction(raw, sender.spendingKey) diff --git a/ironfish/src/testUtilities/fixtures/transactions.ts b/ironfish/src/testUtilities/fixtures/transactions.ts index 5806f5cd0e..5799b76ff0 100644 --- a/ironfish/src/testUtilities/fixtures/transactions.ts +++ b/ironfish/src/testUtilities/fixtures/transactions.ts @@ -28,7 +28,7 @@ export async function usePostTxFixture(options: { amount?: bigint expiration?: number assetId?: Buffer - receives?: { + outputs?: { publicAddress: string amount: bigint memo: string diff --git a/ironfish/src/testUtilities/helpers/merkletree.ts b/ironfish/src/testUtilities/helpers/merkletree.ts index 7126e520a0..38288760d5 100644 --- a/ironfish/src/testUtilities/helpers/merkletree.ts +++ b/ironfish/src/testUtilities/helpers/merkletree.ts @@ -19,7 +19,7 @@ class StructureLeafEncoding implements IDatabaseEncoding { serialize(value: StructureLeafValue): Buffer { const bw = bufio.write() - bw.writeVarString(value.merkleHash) + bw.writeVarString(value.merkleHash, 'utf8') bw.writeU32(value.parentIndex) return bw.render() @@ -28,7 +28,7 @@ class StructureLeafEncoding implements IDatabaseEncoding { deserialize(buffer: Buffer): StructureLeafValue { const bw = bufio.read(buffer, true) - const merkleHash = bw.readVarString() + const merkleHash = bw.readVarString('utf8') const parentIndex = bw.readU32() return { @@ -42,7 +42,7 @@ class StructureNodeEncoding implements IDatabaseEncoding> { serialize(value: NodeValue): Buffer { const bw = bufio.write() - bw.writeVarString(value.hashOfSibling) + bw.writeVarString(value.hashOfSibling, 'utf8') if (value.side === Side.Left) { bw.writeU8(0) @@ -58,7 +58,7 @@ class StructureNodeEncoding implements IDatabaseEncoding> { deserialize(buffer: Buffer): NodeValue { const reader = bufio.read(buffer, true) - const hashOfSibling = reader.readVarString() + const hashOfSibling = reader.readVarString('utf8') const sideNumber = reader.readU8() const side = sideNumber === 0 ? Side.Left : Side.Right diff --git a/ironfish/src/testUtilities/helpers/transaction.ts b/ironfish/src/testUtilities/helpers/transaction.ts index 1c44bea754..5b43350250 100644 --- a/ironfish/src/testUtilities/helpers/transaction.ts +++ b/ironfish/src/testUtilities/helpers/transaction.ts @@ -32,7 +32,7 @@ export async function createRawTransaction(options: { amount?: bigint expiration?: number assetId?: Buffer - receives?: { + outputs?: { publicAddress: string amount: bigint memo: string @@ -41,10 +41,10 @@ export async function createRawTransaction(options: { mints?: MintData[] burns?: BurnDescription[] }): Promise { - const receives = options.receives ?? [] + const outputs = options.outputs ?? [] if (options.to) { - receives.push({ + outputs.push({ publicAddress: options.to.publicAddress, amount: options.amount ?? 1n, memo: '', @@ -54,7 +54,7 @@ export async function createRawTransaction(options: { return await options.wallet.createTransaction( options.from, - receives, + outputs, options.mints ?? [], options.burns ?? [], { diff --git a/ironfish/src/typedefs/bufio.d.ts b/ironfish/src/typedefs/bufio.d.ts index 0fd6b419f7..bb8a1f7b93 100644 --- a/ironfish/src/typedefs/bufio.d.ts +++ b/ironfish/src/typedefs/bufio.d.ts @@ -20,8 +20,8 @@ declare module 'bufio' { writeBigU256(value: bigint): StaticWriter writeBigU256BE(value: bigint): StaticWriter writeVarint(value: number): StaticWriter - writeString(value: string, enc?: BufferEncoding | null): StaticWriter - writeVarString(value: string, enc?: BufferEncoding | null): StaticWriter + writeString(value: string, enc: BufferEncoding | null): StaticWriter + writeVarString(value: string, enc: BufferEncoding | null): StaticWriter writeVarBytes(value: Buffer): StaticWriter writeBytes(value: Buffer): StaticWriter writeHash(value: Buffer | string): StaticWriter @@ -77,8 +77,8 @@ declare module 'bufio' { readDoubleBE(): number readDouble(): number readVarint(): number - readString(size: number, enc?: BufferEncoding | null): string - readVarString(enc?: BufferEncoding | null, limit?: number): string + readString(size: number, enc: BufferEncoding | null): string + readVarString(enc: BufferEncoding | null, limit?: number): string readBytes(size: number, zeroCopy?: boolean): Buffer readVarBytes(): Buffer diff --git a/ironfish/src/utils/bech32m.test.ts b/ironfish/src/utils/bech32m.test.ts new file mode 100644 index 0000000000..2c9742b55f --- /dev/null +++ b/ironfish/src/utils/bech32m.test.ts @@ -0,0 +1,19 @@ +/* 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 { Bech32m } from './bech32m' + +describe('Bech32m Decode/Encode', () => { + it('succeed encoding / decoding', () => { + const encoded = Bech32m.encode('barbaz', 'foo') + expect(encoded).toEqual('foo1vfshycnp0gv5etqu') + + const [decoded] = Bech32m.decode(encoded) + expect(decoded).toEqual('barbaz') + }) + + it('returns error when failure occurs', () => { + const [, error] = Bech32m.decode('broken') + expect(error).toBeInstanceOf(Error) + }) +}) diff --git a/ironfish/src/utils/bech32m.ts b/ironfish/src/utils/bech32m.ts new file mode 100644 index 0000000000..f89d62aca1 --- /dev/null +++ b/ironfish/src/utils/bech32m.ts @@ -0,0 +1,38 @@ +/* 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 { bech32m } from 'bech32' +import { Assert } from '../assert' + +// 1023 is maximum character count where the checksum will still catch errors +function decode(bech32String: string, limit = 1023): [string, null] | [null, Error] { + const decoded = bech32m.decodeUnsafe(bech32String, limit) + + if (decoded === undefined) { + return [null, new Error(`Failed to decode`)] + } + + const bytes = bech32m.fromWordsUnsafe(decoded.words) + + if (bytes === undefined) { + return [null, new Error(`Failed to get bytes from words`)] + } + + const output = Buffer.from(bytes).toString('utf8') + return [output, null] +} + +function encode(input: string, prefix: string, limit = 1023): string { + Assert.isTruthy(prefix, `Prefix is required by bech32`) + + const bytes = Buffer.from(input, 'utf8') + const words = bech32m.toWords(bytes) + const encoded = bech32m.encode(prefix, words, limit) + + return encoded +} + +export const Bech32m = { + encode, + decode, +} diff --git a/ironfish/src/utils/index.ts b/ironfish/src/utils/index.ts index 7ffa9fb3d7..39abd89653 100644 --- a/ironfish/src/utils/index.ts +++ b/ironfish/src/utils/index.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ export * from './array' export * from './async' +export * from './bech32m' export * from './bench' export * from './bigint' export * from './blockchain' @@ -20,5 +21,6 @@ export * from './node' export * from './promise' export * from './string' export * from './time' +export * from './tls' export * from './types' export * from './yup' diff --git a/ironfish/src/utils/tls.ts b/ironfish/src/utils/tls.ts new file mode 100644 index 0000000000..a037a5eabd --- /dev/null +++ b/ironfish/src/utils/tls.ts @@ -0,0 +1,58 @@ +/* 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 { pki } from 'node-forge' +import tls from 'tls' +import { FileSystem } from '../fileSystems' +import { Logger } from '../logger' + +async function getTlsOptions( + fileSystem: FileSystem, + nodeKeyPath: string, + nodeCertPath: string, + logger: Logger, +): Promise { + const nodeKeyExists = await fileSystem.exists(nodeKeyPath) + const nodeCertExists = await fileSystem.exists(nodeCertPath) + + if (!nodeKeyExists || !nodeCertExists) { + logger.debug( + `Missing TLS key and/or cert files at ${nodeKeyPath} and ${nodeCertPath}. Automatically generating key and self-signed cert`, + ) + + return await generateTlsCerts(fileSystem, nodeKeyPath, nodeCertPath) + } + + return { + key: await fileSystem.readFile(nodeKeyPath), + cert: await fileSystem.readFile(nodeCertPath), + } +} + +async function generateTlsCerts( + fileSystem: FileSystem, + nodeKeyPath: string, + nodeCertPath: string, +): Promise { + const keyPair = pki.rsa.generateKeyPair(2048) + const cert = pki.createCertificate() + cert.publicKey = keyPair.publicKey + cert.sign(keyPair.privateKey) + + const nodeKeyPem = pki.privateKeyToPem(keyPair.privateKey) + const nodeCertPem = pki.certificateToPem(cert) + + const nodeKeyDir = fileSystem.dirname(nodeKeyPath) + const nodeCertDir = fileSystem.dirname(nodeCertPath) + + await fileSystem.mkdir(nodeKeyDir, { recursive: true }) + await fileSystem.mkdir(nodeCertDir, { recursive: true }) + + await fileSystem.writeFile(nodeKeyPath, nodeKeyPem) + await fileSystem.writeFile(nodeCertPath, nodeCertPem) + + return { key: nodeKeyPem, cert: nodeCertPem } +} + +export const TlsUtils = { generateTlsCerts, getTlsOptions } diff --git a/ironfish/src/wallet/__fixtures__/account.test.ts.fixture b/ironfish/src/wallet/__fixtures__/account.test.ts.fixture index 6909ef713e..a4c1dd3c64 100644 --- a/ironfish/src/wallet/__fixtures__/account.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/account.test.ts.fixture @@ -1476,5 +1476,523 @@ } ] } + ], + "Accounts connectTransaction should correctly update the asset store from a mint description": [ + { + "id": "6f59c4d1-7d95-4867-a6c5-6c036c04f059", + "name": "accountA", + "spendingKey": "152f355fc15ea17a6c33a31ee09aa8f2ac36e13de4cba7b44552b070020f7c6d", + "incomingViewKey": "94e97848867e1234ffa04a3de9795882465c0c84144d1fb8e2a1cfaa8f1a7404", + "outgoingViewKey": "08d4e44f3e69a28d8ff7116babee8e61d2a3c2e08d5a255baa8d16e9d79d103a", + "publicAddress": "6efdfc1ebf3be6c233a63981dd1de3864ebff374d3f655c5e176919b2ba3f2cd" + }, + { + "id": "159289d8-f2c0-4e51-a2a4-0038c9754fcb", + "name": "accountB", + "spendingKey": "32bdc69b8ddf8e7cf7ae677a92ddb610da110549b80216cce9f33f43e2940a00", + "incomingViewKey": "f614e782972d23e82b30832bf22f48b3b1c01119baae09c5f9f0a4c03c8ca302", + "outgoingViewKey": "9dc2947ff9296cfd13e63848abccee8e5dda1da2ef845f231456b25b7288d3fa", + "publicAddress": "b5fa261419ebf6bbea41065d7dfa3f69e278bdf1b5cac06abb2602667101f5b0" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:UVUO2++YL9isHy8u00Hc18sQJAtvFRxUeMu27GjpTig=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:2+5I1GuD6vxR/Id8lb3lSdSAyhTAvBRc/S2Cf/J9Rwg=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675701577208, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAdAvjRR37Wx5HGgxQ0XJxYbmlGi02KSpz1rZTi6qBbEmHtwES58q6uaiTJI/Otc9Kr09pKDM9dXq6KobPk15toxUPmdD1RyluDbI/EbLkwXKw36zVzpEZFyXYFt/V90PFWe2UBAYx1dnD4TaEKxYGY0jWPOK13c6RDgPG+nzuT4IYhaf+OXhU7U58lmOaS42nUGpew+eX19TrLmIZ/JWGKiDjpl9BOMSyVEdC2O4EYO21zr0TIPzgvfbefd9/XfWGYbmLuc1N/1q5wRzW7SeeFSZscTOuz9Kq4TDwgT1Da5kiHiwQ/MIOvOWFYOwFd323NYjeeBDcwf0M9Eitn+l4BYD5MczEor6CZlotOKybIjQLlNzu9RQ/LIYTJZL5TQAbPK3x2YCxDZXeeO8w88DeikbFH2Kf4sPTHkBTOxaOtV2C3xiksytc9avXC9TyYVaucr62S+1HwIftHk+imXG6glRwz9OBWWnWBMplN4fy7Sb9+lS0dM13Jkd45ygGkkdOfX7jglyyIuq7TGNOvMgycV6nlluDpWYPEcxdBTL/6pC1rDC1RjpVs3xctQ1Jd6CJ/mlWrnoFm7XGWi12UeQsZhTxteHE9BIZCblwcChkXSLu1BDXUgf+3klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcAzX8N0bANJvtsGT6Ta7zvWbJO2uP/c2pWSJdGeegmdfrNZ/DX6UpQf1s8OmzK4IwNsevSlYAvu812PtPJTcCw==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "F9000BA23B488E405735979A6C5EBCF6D20016F4DAC208FDB6A160C18533F994", + "noteCommitment": { + "type": "Buffer", + "data": "base64:V5woahg5ewkFR4hWGMV6lfHJJDZSKb+lB3Pivbv9FTM=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:TCpFk8RxeaI97QsT0N+rOLemBRlQ3QU6zobXHuMznZ8=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675701580121, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA8+933+JZ17OI41lCzsFvFeDH2QQNI97Hyny7PZT9WBmoGSwYwlQldpkwlUL5HDe8ZBMenzVjLpqxqiJ1q3kz/Y0GBdtbCwzfsEO1Y3tdO5OV4yxFviX0eha5cl6Kifdw2EwRsebHSo8ywyZxFPx+pmC/MP2MFZ57MU3Srl1TfLAPJzB/HAlXtvXv26H4zUYwTvi5R35u70R/4CQClxzK0y1pfJ+SEVTjvd3iFz/r0KOB5Q2r7vbEunYcmq9Zhj02Xxw4WKLPXN0FQUjgmTXDRcIZ39uFXvxql1Z4ILZil3oKzZVpkiSIFxl0FZRZ3+6odYCDZ/q8byA0kJeG5xw0RPjN0/1fod+0CeCgEHgOnt4pjNMgBHzHdyyrry85bRUT8knSOG9qJ6jUN5A2lPJv2+G09/xe/YEriXO+ztiCpaXGCm13/9GcBbCf+D12tZ27xeA0m+qEtrbzAWU9/vCL87KBrotOOjUi1MK608GskmD2B0rNzXCzNP40u/f5tlc02aZXtnpPs8GhBZgE29SlPW8bKwNsdv8vfvE1mYjgQ4gZOCb/6jnRLvU738iXqXY9f+NCqbrCRdQ+YMZvzrAXrs4XbSQlljI/RnExh68gKzI5Jw/KII8FZ0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwzmbwgLPXJE+x33xUsrkwXcAwTH0673CAlb+OewWx+IeRmgQ99kAAclxgXv+3uC9zvnArM00uaj9Qs3Y1/9Y2AA==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + } + ], + "Accounts connectTransaction should overwrite pending asset fields from a connected mint description": [ + { + "id": "6f19f841-7553-48d4-840e-13664f94ee4a", + "name": "test", + "spendingKey": "4a274bf179fc7960842250f41a93cea9cdf65aeedb2acc1862dc517eabaf5c3b", + "incomingViewKey": "1f2a9cefbefc1335acec69314a96a55f0f2c9f5920fae90a9b8b634ad4882203", + "outgoingViewKey": "1a45428d567a6db3aa0f3e87acac24c46efb2640eced73a70d42888e480180d7", + "publicAddress": "9e7fee8653d5f48a7d5a77a6b447b40bbb45ca5713e5ea4aacdbe7e3cd336d2d" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:EBqAHV38mEviW1ipcQN9v+lqcZiOLlDKVBPpoZAA5GM=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:jq6CUuZmNybisJ3RB2oHDT3iZy3Fte9SiZt2tcEOCYE=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675701580675, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA9ZZ4zqQ2gZc02+7TW7gGC2Jn74H1Vm3nDNff2HXuYRCl8JH7aCajekCt3tKvfCp22KkYHqshZAQ+7goRdirJ2UZVKcWyvNxbM++Sbb4EIr2RPxnfWYQXVnQCcI05dMO8jyeHJKSeZRJhBdpA7AM5dHcEC+3izJ827VtYZ9ljZVQO/tydjiYcKXogIwmfBx+Xoq/tteckD1jwVhndEckCnTHiCK0ciCH1CEBh+FvYmA2iJFkZm2fVE5Vrkmq5pkXPmhpMXd24egFVaAKtFIU9ssQl7PeQNy4orIqA0gFS0uU+hYdZozqEQK0byvPfmCOqwtbVyzT7XCudVRY7BhP0qT7M0v+6SxnHbeVO2WTGo0PAtLpK9xB5JHK1/riuwKhJhhn/CK/8n5rLP2e8ra42SBGIbpIsNA8OpvFcjaRJM7i4ztM8wPambiXT6MY2t2QxEZ5KZteXLEMttVqDlo1DIZz/k7sFWgxYtgB2Efg2NYt5mZJnHhK/lxXzBEB4aibv4NX4WdCwEFyF1AfOTF2e6Dk1XvKSIiVQdw8u5uCzboEC57X0wN2QmgW57F+dHZuytY9PK7FUgcPbPfHixNjusHoBfCVzKCrK/985zUEjwIAL7sXjTU47GElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw/YX3dyEP6+aGuKLiIUq9qNYhQ7KMtS4kFEeHFCXKMdUHBWqL3JZV9ANna2qCI4/5TgbrZKyWYCuDfPlq45XuAg==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "BEA05F3D0FF149920B43D7961CACF0DC9E6A48DC0BE40197B80991CCEF48E0FA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:BiLaJl9XB/celNHBqzE2PLwIWXX+sX2xZ0igqHx2Q1s=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:bCtYSrp/otXBxaQqIM1RWi4+WsJ4gKLyIZbDsqShA/U=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675701583683, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAnkqqQAcb7tPK0Qrrm33LSElxZBLirK/Q/yNsU70bLwSI8UR5vRzRaDBzE8FT2XlWxga33p0Qwt4PJH6cqZRSiBfVRhGb7uV5O5woBz0wPNqUifiOYdVjJCnlpWcJctTQeCKiuw6fIdVcUnV/ah7ynoEmC84/O9Q3y7KBrb+ndSYU314J8EBxH1tTOHdnM1k8yiz/O/Tj9cjwjymzHZ29zFwHsgdS3p7zkM3L5as3td2qrM/nhIKOCqthuNjC4lEPYXt1ntKsWI9y35PNnNm1PB2f1KlXg90nkSvoS8gCCLYqY8se24YV4rhdNi0axOSt0UBWdZn9sGG3mRx/soZ1JMbFY8iycSc/o8NZNwBFl9eZSSkohWZReO2wUqfgmZUWJQ7sBOI13wyH5xJcuxLujTWEF6czf+q0gWmSRRrjvm0Fwf6f+8cxlVSh0G91w2uWzjCau1dRa8WA+52mXvMEPk1atcjNlyO85k0IoXcMOnOkbdK80Fp3rUMj8Op07+huF2ba2ZSdP5Z+uWQtzVt6N3I/RK69RLUuEolQU97dEgm/lUACwZ2aha5UEy8kZYt5fst4NQ8PZb7hj4jdBV615HunOSFnsl92h0bSVLfDCptcsFnjF0bIAUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIIZ9SI9gHaj1I37yn6GFq9vriyzLdU5w2lSUQhj0u58VqKPbnfqtsHdCE6COKoTZ9A6i+0gki4Z4/SITuEFhCw==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASSZ01Ba0q/dGsZRRfKlHdAoQYkyCxHgPUsyn68U9rzqlXHKi+eTaKRz+hZjZ1IiPn8tyKGfDYZCXSdYrSRI+1uN8v6lf5+ZD6eA2F06fIHej4VtEYynRSWNbZqA4q9JPCm7DOXGLPYtwg+E29oOzjJC+tox9J6JYMVQEHauBWAEVgHShHlJc5udtdFE4rFmsyqEMDe4AdEQqZXw7LZZBc+HTn6b57FpaQVqQFUwVgBK3wI5hby477pm/bI4Wrib9Zrh+fsUpo+wvXGqZLy6gdLNrdP+XITXzACtLdpR4Bys663NxBTr3vo3Cj30ReMmD8yOJQ7eQDUv8kP+M+4lXWdWVfDjyYR8ybT+r4TLMmoUkT1oCA7dtd9uzmGF2FydcA6VKPt/oEVPox4rYN/Y5glNabvTTKJ4z9FFfPO53FaLS00+8daHtT0P8pNxGki2gOJy+KyfA0qH80z7ac4od989eIz8X8aPp0vDXJeH50deWEjINLZ8mq3kBxFOQAXB7lm4JlcQKyKdDMpzmqVua+SfXRNlp+yFU+uYbNT2CMidwEUTxRyuw+fg1zrOCn3KgDybMtNgAs8SnLjoaZAUhx4D1KuesWX5wOlSE1EI02ddLjatZAuGoD03u3LbALrCfJ9amjx3R+sinGEG0Wtxdgxxihj7aSbCtz03eeiKeqz+A06CK1z+P1RjeHlMS6Fk4/2McGBnOfM01UxRFl+Mw/phHEps+wNuGgKP53DXQn5SehAZ24eHodADIydgk2IYkx7P2m6n1kwYDkeEbzyOMhtUpTE4orX8HsBa3MKcg8iZe4HXHi0rlA/aTYdKYTIjLooKersrK59ZJQC8MZ0u3oi75bMxMQsd0FpZkS2SDHqTo56k5UgULWViAoBiqSLndB/WVCnwlolFavvlpNyP24xfyWZQWkfjfquRuetWO1GBn6f7uKa+WV2jPte9ISHA3yq6PjWvtfkYUlrQ8UHJctAJwSc0duHtYnn/uhlPV9Ip9WnemtEe0C7tFylcT5epKrNvn480zbS10ZXN0Y29pbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAABC6kuBaz9GFBZdCYg8lGCSyWXeTKlsD9y6aILN1D7gXWIKM2ggTrBA6taCdIODYhY35UrbC4xmqpr0jgO3rkoKwHoO5jRJRa0jc7Zj7v1QcHB9wIwSS+Fw+2+WhozDZauSEcZSSJa5woqD/praic9OgfCunC2Yq/vdUvddHdJEBw==" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "BEA05F3D0FF149920B43D7961CACF0DC9E6A48DC0BE40197B80991CCEF48E0FA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:xiPCAPDTvII/a8oMvEgw8E+TOrZFkeWrpdLYCwf1x2Q=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:oL+KpfV+LC7E/JrNP+HJs/3V7Jejq3ShoN3lRtwCLXI=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675701584842, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 6, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAQRFdU4oHHeIwxkSVhsVZ/Sx4Q4ISZVQU/ZglSKG9tNeqjEUcoje3bWVE8wOpBoBQf2BUcQF44FFXkKxkbpluV4NzEF2tZACP01+pELbEUTWs3edq3qHFGE5FL6N7B7p8J2eRp8g3L5lT/6oiQ9rkz1y2IAYQnMyCDw9+AZT227UB1lq54Dj+6qQfBQN0pzv8fA84CXNKetdw7qEBUEvpRfcgMjVkfY5k9zSv6UdeB0miLGnDWh2f0RDlFzDet7gZGqQ9uwmqTLg7FTBcJWjnN9DSu5qYcuFmBEHchrVSf60KryvIXsVtjvbvLndm/8Sy5dpztOSmUKfNa7Y7CJLtxOXLAPLE1KFVYde9lrteW/kl7dUpfUY3g7jGnZQBQ/olQCJ3qAxZSeJjFbdkQfxneQG7dw4E4kEOf5VqeuxLn45y1RBPw7N9tdwv55tTbefKpVlK4FnLPOndtcl22xon0SKBmPZ23XqpCItv265b3LqwjPQBfEgY72Uze8YYCobzoFbL1ekFO2VlvzuMqWbe7/XAr23Zr+foj76h3MkktrHvEP53mtdZpZqT7p+AyFrRR3thn7dzg5lF/oHC2fty4MGq8WgmKtsDZ+2xSm0EYmxdvmy0ZDt8N0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYAU/LZmTjzGma5VBEKYqybZYcNvSgRTXv3rmZfWjkN2kbRS3hO5uA+MZvZiAaYLyfVQ+7XISIw6so5Pf4GZqBQ==" + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASSZ01Ba0q/dGsZRRfKlHdAoQYkyCxHgPUsyn68U9rzqlXHKi+eTaKRz+hZjZ1IiPn8tyKGfDYZCXSdYrSRI+1uN8v6lf5+ZD6eA2F06fIHej4VtEYynRSWNbZqA4q9JPCm7DOXGLPYtwg+E29oOzjJC+tox9J6JYMVQEHauBWAEVgHShHlJc5udtdFE4rFmsyqEMDe4AdEQqZXw7LZZBc+HTn6b57FpaQVqQFUwVgBK3wI5hby477pm/bI4Wrib9Zrh+fsUpo+wvXGqZLy6gdLNrdP+XITXzACtLdpR4Bys663NxBTr3vo3Cj30ReMmD8yOJQ7eQDUv8kP+M+4lXWdWVfDjyYR8ybT+r4TLMmoUkT1oCA7dtd9uzmGF2FydcA6VKPt/oEVPox4rYN/Y5glNabvTTKJ4z9FFfPO53FaLS00+8daHtT0P8pNxGki2gOJy+KyfA0qH80z7ac4od989eIz8X8aPp0vDXJeH50deWEjINLZ8mq3kBxFOQAXB7lm4JlcQKyKdDMpzmqVua+SfXRNlp+yFU+uYbNT2CMidwEUTxRyuw+fg1zrOCn3KgDybMtNgAs8SnLjoaZAUhx4D1KuesWX5wOlSE1EI02ddLjatZAuGoD03u3LbALrCfJ9amjx3R+sinGEG0Wtxdgxxihj7aSbCtz03eeiKeqz+A06CK1z+P1RjeHlMS6Fk4/2McGBnOfM01UxRFl+Mw/phHEps+wNuGgKP53DXQn5SehAZ24eHodADIydgk2IYkx7P2m6n1kwYDkeEbzyOMhtUpTE4orX8HsBa3MKcg8iZe4HXHi0rlA/aTYdKYTIjLooKersrK59ZJQC8MZ0u3oi75bMxMQsd0FpZkS2SDHqTo56k5UgULWViAoBiqSLndB/WVCnwlolFavvlpNyP24xfyWZQWkfjfquRuetWO1GBn6f7uKa+WV2jPte9ISHA3yq6PjWvtfkYUlrQ8UHJctAJwSc0duHtYnn/uhlPV9Ip9WnemtEe0C7tFylcT5epKrNvn480zbS10ZXN0Y29pbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAABC6kuBaz9GFBZdCYg8lGCSyWXeTKlsD9y6aILN1D7gXWIKM2ggTrBA6taCdIODYhY35UrbC4xmqpr0jgO3rkoKwHoO5jRJRa0jc7Zj7v1QcHB9wIwSS+Fw+2+WhozDZauSEcZSSJa5woqD/praic9OgfCunC2Yq/vdUvddHdJEBw==" + } + ] + } + ], + "Accounts connectTransaction should correctly update the asset store from a burn description": [ + { + "id": "57615082-b708-482b-b690-2cbdbad11fa6", + "name": "accountA", + "spendingKey": "fd2b5cf4b0c9d51e13a3771222491fe8042e92060fb59fbc2602e7e2e83cdf56", + "incomingViewKey": "45c47fa1b5225fd47267def852fb7f29449dc4f93cbce7a89ebb1fc8e2d4f102", + "outgoingViewKey": "915c19e26b0472dc43671594b3023cdf7c7ce3f8cb48233ad30b8212a77e2496", + "publicAddress": "9343f9096173632477e5b5de9b295d650041c8c2c099e42c5dc567e01aab1e8d" + }, + { + "id": "9fa68bee-7393-43f3-b9b8-b55b0f7749a4", + "name": "accountB", + "spendingKey": "2e263f337d563abaff2a2bbb00a7ecc971cc6d2c2f81141b58fc4b3df3818159", + "incomingViewKey": "31537599d64f5de8643a1eda2aee66bd0978af7c04e20461b63035a69fbc3601", + "outgoingViewKey": "8bb0b8dcc07bf7698fdfd0657518046a8f7f425b3e1ea3308cf4a5f9ccce4bb9", + "publicAddress": "6f0f558e6e33e037d9122cea8f173f53c5ac0c620b790f6b37a74a97100f9a6a" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:c31d17NcQQa2inOBd1rdsaZgUxXlZJn2ozhZ0XCI108=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:+QC/QxNNRXdCAnmrE27LZ5JVio/7w6jQ9ZsCP3wUfxc=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675701585377, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAANoTxP3zpGJ33jfJGi1twkfRkpCQsTniSHLd6Yv7sD1+NyOrpUh7iWM2vcivcEpF2T0hvEfYq1Sb6GSliO062diSz60O0nTKNgO8beh/wvYytGFXQuRscwg4QDMF69x9ilkkhK1i9/MKIdYdWwJlfo30BYO+tcu69BE2JBhXNfTwCudH4pB8epBcTQmZcJBfop/2rcKGyinWbE8MBo7pehW+4zMtPPsGjVTGq09cUhrevAshEHs2Pn7DxuVcvdMKyKAzDe8Ai/C9DE9qjZ8HU6eVIN75lvgl47IuLoaP5zjjeH+TWcCUOfR9N53EKyADWPxVqM3Q7s9/QmieH0qL7o4WTh8OHeSDg2L5R1qyFAmNn2F7W159UUzPzio/ZVclnK99844+Cx7kFL2MyAx3rWRKQDjsTcMqI/6DkFFBZAWVt+cZjjFlJgJw6dHB02srzNsbKbWt9dHvB1nu3Dp/c2QBmv5DA2xXWc2FLK++T7tuRLYrDQ3JyHZlAe7oB3KPdGjlM+twZuPmw3jtBUUYFct8KG7YZ/6niCHde8xrCMKsKpTmrlblW2FrDSAa9ru6zC6JsF2KLaxFAp1fvWjtK5G4LksD6pAKt+NMI6KuQP4xelunu/oBNhUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwe4rTozcqaZD8xy35utmgf9sFG5A3jCNBCNgkgCyEihzLbCicEyUohojDdAUBtkq8gnPrLJLsD5ZbzthEipOrAA==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "FF446BFDD01A4A1B5290809CD424B3A49C464745674192328B51BB61299AF318", + "noteCommitment": { + "type": "Buffer", + "data": "base64:065VagpJpZAZJhK5e9orxFk9QslJpgPuao16GKOEEmk=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:q5ffi5bFtBzWjuxABRd0V/QBDKEFs6e2hWL9iLz9i9k=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675701588201, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAzOV3EmtawxSXnIX7qrZxy8EAfP4iMf5CW6k7hKXgFJytSNmes0ohmei0fOK7V4MEzFwMwIBUOmgTzzZ91uTywm5kRzOd1ppeu+0EvEMlawyS8uEFBMOLZ8ka9wCoAZ1ttJ0zdxx/rfbfG42c6wn3Y6kP7grc2ovZMHSpWHRFn1AMalw7rJeqp3WOHr7dLH9m0kJS+Yksc3OzZ48FYh8mhjYb4Gy5B1tFZzhuhrShCQmx6p3BWaHZMYINbVmUY7iZA2bTgBiH42eWxdGt4Fyk+jGVGSYlEO8C4DA5UMCByb5aMFNqXBVJ66B3nGvzWdU2rrxiNYD98todgd96jgiGXcYS8ZVzGZ8FKr9+U+ykpGwdwXx3Bh1YjKE7AJFVDqVDLesex+qa/aWDc1d7nTZUNtww+57Jn7zrVuM9rEC6dMltsjpkrRwXHLpNQYPCMUz6YXBITH6/5gMSTrdqXgIlvFFW6g+eRo33kBUd2sEPQSEGtd2/Qd2UrsneWtwgqxKkYuc7jiDNpk9CAgcwR4D+umXQ5X5A/sSJtQLBlL9vSiUaKs8R64CSyqhgrPKrhn61QX7n4Ogjjm2crrkHwbxGj627jilja1yWo2j0SqHRnPDjHbfcmCOqdElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwE7NN3gb0vz3EbX6dVGyJ/V6W0vmQaPZ5sQ5obqfc6qizwmUmYzrIiO/wrifjp8+j60p5911CXJ9qvTw852rjDA==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 4, + "previousBlockHash": "0282CB85FD63FDF85CB41969EAC954B9378D3F41FE1DB9246B00527E5640E5A2", + "noteCommitment": { + "type": "Buffer", + "data": "base64:a5neizzfDkEqr6E9mvDgY/IrUS/hrkGWyM/J2FnMy0k=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:PzSkq9bPoAyZv24kh6Dk1cg86x+7AzsYnd4cVbLQMp0=" + }, + "target": "878277375889837647326843029495509009809390053592540685978895509768758568", + "randomness": "0", + "timestamp": 1675701592454, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 10, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAeXL9un2c1KXzAAKc8AQz9VKPJ04NpLQmdvm1s5R3QxOXSKidHl+uXT1Y3p26uk1s03ELGXYyI6PH++UKKI//dOadAOFz8oDqT+p+4QCSJAmWTcAMAr1+alT4hxT9+MpALM609KTkerEYCMYtIOwK13ZjDD/VW7bUy2IjzmxdFg8VoDcfgTs4gU+bcWu8V66zivgGlZ3oTDybo/O1r26RW2B8zlO4RGV0TX4e9WdQQZOshezTFfwIyoHOrQ6bsaVsnuVaWNypHMc7wQ2MkJ/QrujuhKLAC93ZmWUGTMqMLZ4GZP9buDdlXskyh3OGZjH1RYenoh3Y+O5MSluChyYdA5pMEI5LJ4URVEB9kVogZQ7Bbe1lKLcWi6g7s85nY6Q+hS22qC4iS0NVaCSPXcxinMJXnoxD9F7NS0W8qDNKOU1GnUBpHnDJF8xYtTqbc2DQhWtq8mQEtA5EcP3+nIAXx7wfdrTCRG2i4V2FPCz1HZk1bh2/HyRUusGMJr2AJKNrAdgHt+h+xAu8s9GmgL571jq3NAimqm4MyZZsJoOBSD00QTqqN3BRJpedUdd658LWNE6AtCJSwL010toOiHEDdjfGRXB5ShCQue02t8xD5mFxxiCAeusCQUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwDPAcvJGgnxUqd8chVURr1vsYhIJO32fCHHOyGFlJSefT7PhCBWAolYDVK5HyeTE9qZDHd1TbL0VbsIRbMEoGCQ==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + } + ], + "Accounts disconnectTransaction should correctly update the asset store from a mint description": [ + { + "id": "4fb988a4-25cb-470e-96dc-0f5989d8befa", + "name": "accountA", + "spendingKey": "f0f32ebc7769c6a4345850bc1a67fb5e0b969224fcef8da42628a77153d13328", + "incomingViewKey": "f4c06a46cc776b96fd5704d201c9fcc2a0af89505b214585f5a39cc087b1c107", + "outgoingViewKey": "06ad095712db65b58b5e92a8d13318090dbb8ba7f6925f6ac72e46fcab8669f5", + "publicAddress": "ca62b42c35b40f35e284bd44af9f34bd0a2ecfbf011d6e2529c2188a0395f5e4" + }, + { + "id": "78cd7cec-7885-46de-a45d-769bddbc670e", + "name": "accountB", + "spendingKey": "a30ec12f13f4b8765844a1d3151c5f2b126df92533a6f692529ba3d695c22b27", + "incomingViewKey": "cbc4eade2bbc6dc3b2cab019fe06923765f72c2b78ef23ec77e548ed4110a703", + "outgoingViewKey": "c015af17cec34fec38a1ab414e026df1fa2a3a337a4f2cdcabacbfaef3007612", + "publicAddress": "b761aec0abdb7ceed781400916bc6918b52b84e35dbc1ae2978b59304a4d9750" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:XdieopgyFeBH6sHV6oaf7K34heKIs57YWznfFXKsMBE=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:ysVBLcEEw2ngR8LO95YLmE3lMbj253hZolArSoqPCOo=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675701593546, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA1xnvPMNNbbEhfyUEeZ3zFOrfT/vaKPmNrNIm+rz8KL6oALGfQaq60a+BZrL5ZCnJP3cJuIDWGNZGVWNGoAXEPtX689sfgq7EGXf4RtIIPjaJWEXjm57YtRAVOs8Ep5Fku0s3H69a8vdAn+xKPuTyL4aN6MbB9Gbg8NQk6J8qO0MFyXXC2/5fi1E7hNkGKLVQNe+I6hKmKUK3Ecawm8lhOelYT960wb06+BNBz2pQwRG1Tp8B3/B8YhTNneNA4HIKY+WAKsr/gBQlKuwaKzsiqBSHA20dWPQzWKB2fxvnYz6qQEFIRahVDJNUOfsKZoVcqMlv4TPSPcfTDfy1mGU2w0wrGJbG/zBysSQIDXcOL99Fj60k3e9CDes+F7bYyS8bN0F1Kt1YZBCI2R7kzZeeDzyWXETBzQqy44uVgby+IRvUrCxKNOKqPpbsxsg0N0XqeHXAb9nEEkomVLIUfo9OH8d2znABlUmyVv4p26vUBKVWFDgkKjswnf+XNuIDbO2as5NkcMdB1tchT5mn0Wf0FKV0+MysAo6hjNDE6zECiI21+0kAO5tVo4u3y6LC7uYjOJ1YAkaVDhxBiHXNVY+YJMPzXNDp9LxYEsOweOF6H6bsafbB4/Uhhklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw7ryLjWlZG4dbRfU8VDMpFLVfPsxZho48eiVWvozvzLgCSPWpGBynIqcPFp6jOwFcOm4bP2zi1dRZ5z4HpEKiBg==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "C341383FF71A0BCAF971A96420276A63FD0996464C91893B5AA46B8B1DA07ECB", + "noteCommitment": { + "type": "Buffer", + "data": "base64:2UAm/mn7zHCIjrA7a6eUsf8zdtpu3BBUATdLTILem1Q=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:idS6QdRGJNYPpwdm2E0lhXZ6NvjeCF9RkkoetnLDPKw=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675701596465, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAogdFJIreGTkBvCtVeYy+1Gfu0bHQ5m5L1neAqEyS0GGXbDYilmahOvBn882M4RQKG9/rT8xiYYJfU9OABPCIub8jmSFwTh74/7hyH5fjT3WEfkW5+s8Tdx7U8vY0n9kLuhb+zafCOdUbiwznBASHezhT3+6Fx5ZAW5lzW7xcd6MXqNYjumKLI0wJSe/fZ9C28quhzeoz0kgj462vP9WA3haKxs3PZeC/zNoo8rfX1rWBPnRDZdM3YI1Kzd7tD1OiBXkkQNH1bnNuQrLEgt1JtpUNfcL22owv/QCUgNWWF4YbwV51Gm5sUt3giGK45X6vohkQxRdESDYPLu0Ni26K5yy3x4kn+BhLyPERxfq+HH+s+VZ5Nv961OowG1LHjpUAil8OV/qJ+OFPqpjjv/Kp3hOniJ3pu0RcZyQjFZOIEhC3fZV1SAY+/vjyN3ogQyCyqO7jQc9cPVcvK5+odG9cJzmkKAAj8Kk+kvyVZdePojZ9d7SUag1bCRMfZ8IAFPrMvVhIh4hQT3UVFLZaeHhJHMdiAehsETY6HAOClRUvgV2peu3X7qepscaENQIzC0ZewbDb8b3KANm7sYO9AaRyGimX0P9zu6KMY+543rR4n5ddpOr9lN8Wuklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw9D4uRuBzcoIE4wzeM2JcQaGKxockPGzenmpRiNHkpXNVWt1faCJxAhh1IW/+Q5m4s/qqS0mdh9VBdZCXaAPqCQ==" + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoTmgqxufatbtbOL1oVIfY74XCzXsitUn2U9JS6VYZCTbR6xH6LiqNXFMVrSpF1sHA1C/o/vTSRVM3OCqdwjkFDshloo39ptkEOabqAHx/KQjYEqxUJ7LU07rLoykTOjoNjtqISFK9BIRUVYr5mDvfY9rmqr4VlnDxe7kBCnFYMUX2edbiFFllVAwAt+0DlJI8QP/E0GagvKkAIe9D9OLZdVq3Q4W/na/zffMGLUWHOtM1xpYBa6lFRAiMPhgHQ2pWhv6i4QQtwz/A89RKNc397q1uOaFjY4jg8mvafvBwpfu1Kdq9rFD2T4RKYwc38H/53eAN40V/xnoNCIdEGG5F3YnqKYMhXgR+rB1eqGn+yt+IXiiLOe2Fs53xVyrDARBAAAALl4tE7+6ntpvkEPP8mW98CuW5nF+9IORr7Ai4110Vqut92cqbjeJh2Ri2pLF3QyW6q/9r9gj8jr93/Cx9KC22IlGtDdeS3Evc665OTQCUEpdJPvveViXb3KCZev6Yo4BorcjZyVT7YsHPODONGGRBEsYlqWWbqj7mM8sR5nIDGruDvv6oITwbXBH4bbypTNTql20IK7q+3hfVLCNKWkv3etTJm9YvPU2A3lO7V0PmY2e7fcboYL6ehNTwUw6H1WggyN4Lj5IlzKU0+BQG6OJOL6o5a6PjOf597pXtEBffSJ6xZv3RlGE/VBf70ABa1wObEYmrjVOsnu4cu5PXHD235x31PKYmvjawl+6e3KZCGhjxkqZRb60IWn05hwzhF/5VwL7csraiAA7Vk61QX+jV/3WPo4MKs9TPCD2sCBemUnWzeqySUu+e+b9/2fTPRT6eY/eCAnBF+muQOsxmKCkw6kG/gsQmyaVjk8oavLtEz8oU7QHYl7l+5Yrg81JAXSCThVJG3/+EsflxSWfU5HcgxtF96HqL+bFtUHCtr7tgZuxPdz02V1fRCBv2QH6fTP5VNvJ8PheeuaIpwfA8NydVxjn9zhDhprmqloLXth/d8VSt8syTFcMTVLsfGGd8IJgXMkHn0Shm22jd5Etj23tuqxM96JfG8CkqJOFltrn3ZOAtgdsq2ewOtqZaWiIGJqIm2hfJogc1JL7X39o4CcVef5tWnSHGmc34j5VI6iDHAHLLmJGspOFg+ebk/0EgecMirqiyu5515gY9+XrmmhGw+IUFeWuXYPoRemk/T2nCf3qq5zPhOwZSC5HHaIul0G/yx6+p8Jsjf0EbFireZAPaE59qsF6G/PAxJTrd5ydaZSInhjNDsTdGGKEXuDv6pj4ecu9LO79BfZZ2oN4q+290he9ZVfnWzyU5HgJLjAUu5yJ70eF9uSfpwKBNqq36eRx+uQxvewLns1hcr/M8dF89CygSRrbasLOmQjbIg5/E5FL33i7fxAXdawhrJRcdkIs4CBvv0FBhKmLjxNh3XW2U1wNwJUhHiQMJ7305bK9LZmv8odZXKB2XwF8rZ/8aRLsIMIJsYDMlnZ6I9v2qc3hvlUEbssZXRrTDTR2U9lwp+BH+mo0ojmzpSKUoft3IgZFkVYa6WypQ5fRBxftzfkhQsrTfoNjkOQYk5n6ICoZrL1xpI8J7LITl9wmKK+6aoijdP6K7HwAxMmlqaIpKFtt5DmsV5EUOphZDznQypsVmzh6gX50Hu/bOnDY1+8Id4zaLQ3tatshCxOkRk6kDu4cWOyPOlWDg3/VSQXMeAZ/gNs1hI//XC7vWkX1KEJCNG3gNyNkgXzipweegmc4hQDFBZZjOlHMOIlja5rM93HgbPbJiWZ4EfHyduvtZliHU3QUUMYnJxWFYqcItwEs/8+6FYhUvLNuzsj8/GamjDlhLQhRR+88oa7hc2sOrKTtyqnvwu80CWY+3WzarGFOIS7ndbSUnfHIed2bIKbgIUtQVmChLcQmYPr9mizMr9O8ipuBji60a9vqCdSuLHE9jRDLDd6coZ+S7LkX20GnFFJW4nekgGgBkVAW5nLpxRHKX1r6rce7CHNJWnKXs5GR2XycJEAN2idmmZl2otqNZ3igh8HBfUGUqhXQM2rs4wQWU1l8XbGeVT0Rv9AihDvSCY3FuQN8cNTMAymB5DYuAgYfgUUqfJQo2Zg+3Al8e93enfQw1hnf6Q28AsC1RjJh+IOwPpNPU7jtxtuV38soXRQacwJymK0LDW0DzXihL1Er580vQouz78BHW4lKcIYigOV9eRtaW50LWFzc2V0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAeGRBaQyaNraDDuhYSqyF3b50JotGPJd4Ku5JGXXSBV8+Q0GI74Z6O/42TOhgNojsM9dm0dMktkuRsyBNIoaACXUKInLLueNHnWJ4Mec2WfB1YVqSsnOEeSKUquEVA8NP+H/cV0p0Npj0a+CBNo1sXgkYYh+dn9rLE7nrily7YDA==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 4, + "previousBlockHash": "48F61E555D9633B8B365BB1097848F7FED9FFCC89A3354482E17054E5C201EDC", + "noteCommitment": { + "type": "Buffer", + "data": "base64:J6B4oqMz4Wi0vMxTMBXrwVqOUD/QAcrnFvzYAQdK/WI=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:2rD+Www52m82uEU3kCWB1SNJ869iBJXyP+mqAbO/GhA=" + }, + "target": "878277375889837647326843029495509009809390053592540685978895509768758568", + "randomness": "0", + "timestamp": 1675701599283, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 10, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAMzeGDyacrGv7NObyxzjqg/jLJwny1+Nas+uYFHIDSBah14t+i5+ynD9UZG/KNF60fdwiVn2xrW5g5ISotAscNZTFwYtMUMemAyLTABX/Kmq4k85+Dc1uVIWlhtv0iX8FJ+o6ivKUB13w86qDxyCu2etd2gsfq/zvnfHYKr9bSJwIXQRD4ynJ5nNY2eSTwvugsbaoNFC3ZN4FeZ272hejHWOQkQO+sKqz/417Yu7Lxt2ynpRCTvlemJE97l+6Lc2KEFbkqUJofxgGYgy2TwuWy2ZWMlF249N5k3q29vWx/egG7A+T9sUsEj+O2v1c86bFFvjcKTPm/nB+FW+qGBNMOv/gBmuR3wBbQHMpzKjJOrInuLVt1e2XfX9zLkpn7uJeUFMIPINnhk8MAAxXWUZoyNcqsZTFFyCbrBz6pKaA6cJVOKROdaMZYcIMa+nSv/vpy/ypq8jKfFIFuTdISkQibZ1urzbdxOrchDZJUKBnN32tim6OoSWELTZ4009U9xd72HbG3vsmswShZAyXnr+ns77llssMLTFHcWSGVKWSHdGDVxNv4qyeELPWRxVhTHm7Ad8HZ6sVwppNZdukJJuakcJ6F3kk88p/n7w2YflFsF1Gj3VQQ9CyyElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw43MQsnZiz2VKK5WF/Y2v0R9CqIaD3wa2DdCZFAvsW/Pv/hFZkkEoP2ew5OrbpLcEQLiDE9FqMs/yJU8+f66WCg==" + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANMpT0MBUD9ZDrUlrR8tureDvELZWFOhW2FL4fmMiFL+G/D9a38l+LxhVETxaux2vivilAPzsuR6+zms21685neTz/aHMPrJYAoG7o0ODXwm5UuPW+/QXujjfXJ7nPm9xbKSPZzLurOJzh5ccS2vuoAKeaEsTkWcmzLvPUdUjPgAAihYpaegNd8z9+0nHxiKj531ooDGSyr+qb59txkKRK21ZPd/2KLmnzBq9SBnx+sqtORNYkXj04gK8H8WjZfUhsqmscWxTWj6noiynlzy8foYwn536oD/0DSwyEJEEZoH5fOa9lvDhfg61kTnw91dgiz6TQLNw4Dt+gLcFXtKmINlAJv5p+8xwiI6wO2unlLH/M3babtwQVAE3S0yC3ptUBwAAANquFXa3F0MtBIlyc4En6Azwl/NeQVRNG0FJQCCRtjmu/gFMKC6J1IloYpN985U8lt0RYJEtgH+pRKOqDZORuUS4HwAedrBepWOVM8yBFeNRZslwM9SvBYWJ5ZouXYMpCZJqf79fpTRgkAivYLr278Lm8Bz1vi1Sihx3Fch9mIZYx+dU8wfsloQR7mGOHYE5XZaOQEkudVzMEr/hVnkKDi71JSclw7IJcWsol5lBlAeNICbsk7h9w1luiyiO14lFVxaNEPDYdDaPzp3ePJUeq0Dg2YIZ+fFV/TGHk1ygEtuywGo7ZFJyJj/CEYQ/tuZcn4JLf3lESa3spRCTXsHYagoi6SCLNrIgH/pmPUYA402QDnGfxDH7hH2vhA2OU5ctv6HNDqlQfK2eU/bZvXWU8IQrV1UvKB5bbMIk0O+x1z/RpDy2UEO5mMEAqcv7qE3Rl7RsCJcgpdMVKocT1Rz9owO3HN911Ued9yqXydC25kxf29MNt670AhFLr/7pr5WiyZSGr0MpNnV7x7JAUWcISjRLmERINUARV/WOo+bohhJmUku/ND4EU51W3ZIRgRwRF7VjCFStx76N7ahp4+SKrMnAJEjQEYzdac0d21paG0FcEUlv498wHGFxUJVdzKaXmG0aE9lc/q+91B++Gg7bil7gZNtMHwEgKN0VNQbaj5w5dsiVxlPpu6ke6ZQMEdWUF5gqqUwDZQ2j7iCUbtok1My3vuljiAKspK5GR+mxm3iCkorahoDzhihDwdHK5vN9JU22PWMDl5ucSoZ6MDZrcFaxDu9UAVAz9/STNCOkmfjXU/LHcdwDbs2io4tV7n+A8JVPiJQpc4FoWpRlC4oKsFSJ9GmJMSaCvI3TjkHKl9qExjaB0dZF2MuiPVi3Zj7FPN3+3dRED25cbhJKQssNPZhqZUUaxRSBSsNtv+ByekwkmwqVcq3KXCcF6X+LZGmuxDEicJtDaOy+bGq75FKpWJKq8N3PBGpZ6AUwfjL2p45rWMY0CnYzMyKlGbs74OQ3wM0+UXzGR3Pmeg7RUdZ+x1kkSw1yMgWY1IiflfcHZuX4YPlOObSg9Eps35DPtYG2C/64qOwxSB6G811f3GRfxdtCVSibfb6DQiDu+nTciNHXmzn1GCgh5nfb29bdIFLgp8Xb5v3m3eEuRPrK4SvlU4dAlXfHgOUqzDljoZ2Uw5nP0m+RfJ67o6IpiguW7eAlShpcY0MRnDiKJHuy9KN8qNTougGr27teRMwZzLdVydt7pjrJHgipb9yUiVw96pMu7MIzNJEktNiHwpXi7GPf073LTUsVmddSekuXruyUvHBworsMhAIR3n43IckGc10YcSjm1XbRkwJb2V7lWYQKG77/sq+zzavb2wLQhGc1fA70l7DruOnNFXehz7SmtAzdT7nri9iZY+LR4lBPQzvS/UzbSAdJl38udGBv9dD/cgOiF1XTnsJSgk3oCdsCcKfmEC0uUWNUwFO+NvpUJnLgD94vZlQavV2ddB8lC6c3fcqVtDaTCVi46wlIi5s4XcQ5kcB3yIRGn3o0X8W1opZxxa0/+irOqi0e4Z9CSGTikSGBkgJLeZbdmxsltNeLCft/ibHLEUXH1mMzK33CNIpxDD3ftPfZRu4KCOuoWihIxdB5CmuGjE6rjQOuSvfN5Net9tnbjbskFiWJAZQfrpT4iJyNeUOlVaIA31a6QwWjPbmpo7lXsc7E5qNcOZPDUL+7hZ0I7ECVK52LMGXwj5In0DjJgIkbTMHL+mX6+zUsny5+ymK0LDW0DzXihL1Er580vQouz78BHW4lKcIYigOV9eRtaW50LWFzc2V0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAA6DGNoQ4ObJyuPK0BaLBtG5XRj+3H/qI923oRg+k7OGke/xGkfoX2rg27BaCiHsXTEgK+tAf8tK2R9pbDXlP0GTLSez3g1o4ErHPnIOBTpt+RLcDiAqhzasaWUY7q+9dyF5TIavG99ee5/46yTAYC1YSm+pZBEUBYlP7tgTvm5DQ==" + } + ] + } + ], + "Accounts disconnectTransaction should correctly update the asset store from a burn description": [ + { + "id": "96e48999-9ce6-49d9-8890-765d147303f7", + "name": "accountA", + "spendingKey": "5a41e768d0e1bb3d04e0505cc59020e8244b147ebacfa1e40162116f272a7937", + "incomingViewKey": "aa468cd7f50f91cc3ab8263ad7158a4ab750afc7ce1e9a32489bc2b08ffd2805", + "outgoingViewKey": "ae762e8f6e52b1f2f506b0078b3a368c7d25adfe6370663b21ea6efa910a2b66", + "publicAddress": "e7517c26986ebcf8a290d92e60a1439310bd1c8116773cd440053cb10207d892" + }, + { + "id": "095ef287-7ad5-48a9-9530-94df3faa22b9", + "name": "accountB", + "spendingKey": "4e12daa2eb89029064586403226c2652fddba39ee35642218e7037d8c97abd71", + "incomingViewKey": "0660041cebc43db11d9076c9d775f60ba451b3e77ce226427f568384580acb07", + "outgoingViewKey": "f4fe2536b6726e2a758e78ba0b86f56b1b6aaab2b69f1dc8859b3e59a91df7c6", + "publicAddress": "f05172ddd57054f0d1ef459fbfa22eb5d6253a5e71668d331508fe23549b0b2b" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:hrElTnjtjYQ/ITuCYhZDVhLRvVn+LjpuaTSwqtP5wAc=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:wxRmPzFLjRocdXUIDt8eZxnA2BAo7XOJuCSERzedMcg=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675701599827, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAA08Lasm30wfzkc2NM+mUuDDOu7vBLnkbwPvxiS/sE6gyyY1A9wZDmN0y/mp6oB7MxDxjP/K3Yty0vedNe90xzQbaduQpXNOr3GL9IHuFHfBu5oqUeNwwZjE46GKnTVL8K9RG8qGzM+JMKMvSSYWhbN8LNAoXrhJVyXdrlWKvtjoMWAcLvNn3dylUD2Lq5/dTgOqEE2hDCIMDqtLFFkh44YChf9s+kAh+HKpamJTr+qryCMYz6c0z4yo+UgEIiGZ5axn2QHh1CjXnqI4YJrba8lTv+UWb6cRy9/UNseIhFa694AbT7y3UO2/tOXjNZOuN/GG1DFhycVDuEBO/9tPfTSOI+U1FbW9xbopqPJOyf/bcLqr3ifij5pPTTAb1uLMdPhPLzjZ7QF0b5a3bvVGDDaFxKcmubvglJDOOV3jyq0FO3T8UOOPKdED0SFVzVnjWmweI0HcnRVhmNaxkUKX7l0RxHyNJN41RiAoCeRBy8hU3kg+52tBZ/7hf138YXqqXlozaFkP7lvt4i3jF09qvZ7K0ps1E6SXqyzfXgphPWMM/7/ZjuAwMpSiY2urkjAiwc/7tkNI3jtiUogs7G8jA4BhgM3vJmoO6I4my3B862vws/IWhSjcvUBklyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwePBODLouk6p38UVT288i1WOGa1ULzYUTrgT7Crf+TQLRiN/hsJVXsf7BgZPTiY5FPQyITNZGhmrisMxNcLSsCA==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "1E2AD1B07B368961C8721547DC03DD75C96ED8B148E4661C8BD8116118BBBE08", + "noteCommitment": { + "type": "Buffer", + "data": "base64:sIXxmrsIW6UnPWmgJFs0ur7g/jgqCtien236fwwW2Cg=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:3b8Yaf9fr2ppsIR14z/PEhPnlT4J3VFCnm+8zhFbzOw=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675701602852, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAiC1iOcqMpUE2HGVrudm8X7dMxqW1WRSewNTMFm4AveqiblvUrXh6NEEaqIqeCBDtO40Qq5/gnnkGOufjFgpRvT/1/npiXBbtc4WybK3Z1wWG5avtTiIn62vOI6Vqt438n0VpyW+6nw0qevpd1WzNtUHx3ZQ+bCpibh8QcUblNgsEo/2flEvB5amDhbkkmLXgafO4/MexhSZR77mTXQ19ktNWc4RDj34n4SDupESmQlyUB/eZLfdJexv1w8hxSnM/KrJMbl3L72Magy3xqw6colQ+GxH16IOT8/Kc8KQJuuhjolNLYYMA1n6SdfaYHKO5FxX/EHIxQedfo0q8RtTFkn2zmsAKiYU7IQnLEaWGK4uU1vhFoai0xDEfTrby1wMy03GxD5NAxpfO2L4qw6s0bX8kk6uGrXf4TZJXMs4gs0KcKxxCU4yUFsJ6wYL1Hla8KjkLgcYKgEGDfeVgf0JboA7yxs4FaSWpLY9fVM3bVSBIUvLV27cKT4IafXbMBIYXSOgf43NzWbXyYtQqT4lnl9G8ycN7ir5T2deZdjYJNY89ma3XAwYUm4aUlSp310gw2SredU5fy1GLtiB2R8FOFli8u259UoTCAKjDcz37flO+5rKiZFHmM0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwhl6FMknQbf/5AqOMhqAgEc+j8Z2GKqwflWkfHVnnCxv9FFg6xzKUCmml8RF1Xt3Wn6/WWcMd1giVFRN/FJGiBQ==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAxU3FfvFywm1/0gj396vVDlGoj53APmDkzxW8OpyZk0OssYD77WzkRPodVmNvQKdv+0opDhsn217ceOLCbRqvXP8uRebrw1LYhVcXIL3ZHhWuYWgfMHjKIwGORcqylTGb7UeGKYB7E8UBxidDrIyBNbipPVti0SZIDrWFQzTw8iUDEGHwchDrHk0jBzlXZMgCT4VFhA0u8F+kSfVJz2R4C7QG/RLeTHgq7iT9zA82WxKsU/zKU4JcBsX9EP574Qj1p1mz0261sH8/mthseimUdK7XnRT9X47RBZTKpemLBvV7gbsRHxo8MvdVLk4mhn0HiAQMBa+Jl8D4cZEAfcivCrCF8Zq7CFulJz1poCRbNLq+4P44KgrYnp9t+n8MFtgoBwAAAPC3QAuqyXQQJqS+g8BZRDsHmJsq28WwMUeect9Ncnuxds+JV3PrN94MyMUQ6Ittt8LcdQ2aSMxQ+pth0Bp3k9azRW3JNWdEVptWjanWsPzi5/4Z/bSAQ5ShzthKZAwSCbU2Lwi6vV1pj9pE4GDkvx2tMfzapZo0+nhkWWbVLfxjExf8DxPiVg5k7VA3c3fM6ZhujrMagtjx1eFTa64AQGi77kYbqPfjfZRYLwVu5fyNfq6zq9bOQ9zclSlpHjts2wuKbAetWy+ZslLP3rgCcCbVrW6OOvbconrBWqyKm6WsHimvcb0297PRKmC1MSeQIoHn0SQv/AwLF70HKQHvxdhWi7NDKHgFzD59YncUh4Qv6EL7+rW5KLoZ2l69vF0sQYMxEvdhyuWc1qJFTLXjKS8LdpLM6F2vwW6e1r74G2rosIXxmrsIW6UnPWmgJFs0ur7g/jgqCtien236fwwW2CgHAAAAHTQxoCtLmxrjCWEEZZwoS3OJZ5U12ZDd8jm9ezW0c6mPNBIuOWMn/9N/prmDe5KJQzhFzqP59nTQk1NkMx9DwMl6MOnXypIyMkwjcbIYouIKheWc85Vlo8Yv+ZYu1XsCjf4flXcArGpZU022WvjbyBi5Q/KVZ1lz8GTAi8qIGkCyhhzndVYIs7eO7rvWUfmctGUFw0OJcvpFXE4J6S2lMAXBDOf6fy4+EsZzw5UMdf9dC4Ub8kA1gvfMBHQRWfclGSig/1ZVkQC/8DTpRN6Xd8gpyK9JXzKzXfiQd0pA3zUEo1FlD/6VFOol4ZA4/gWBoQn7GEaWYq1rqJHc9QxgCkROMjtrBU7gdm2Gyc+aBq0h5vX79xTLq7U7VarDV85s2pnKfCkmu8ZUz7ivoUZHx91nmuwKGbuL7m5YTgoRGbnQiMdugR9ycUmPFbOU9NPNoaX05IoCXuZcW7C54VlhaVecJ6nEmNTPmn6+WP+1fkwuJPYKhSt07eMMjtklIGre6iX4V0s0TRNFbg1aFgBGY+Ehce2RvApx69pXPDxYf9zxkzeaL3Kpuvg1cVzl8xuf1SPHrekB/CV4teintoEVRjhO9cD9HWy4cGMl2VrX5Bn4PE+hzWJuyiw+qe9mCAqnMhJa6nuqxlIp7swsoWTVSRfxNb5fwfXlsHL6OSXW+4hRaqST8hS5qCeVQl7zfB0jm28rDPFIO4J2tZ7dAy5D/wsvejNCzNXBf9cSlc0lPFOu4aFjYWF9PRM1mcA22GgC4hLH07nzJc5OvU2rWePDESPwUXg7vT4G0gAE/gjj9W+Isbu4Get/C6HvxfxPPCnT+9XdewaBi6gVPZct6U+cAXYMwNRyUQQ3tk5eZn5QrhuTDjyHA0ObWLRkmZKdT2S78tDeXm0aKWDJ5emZI5BPwpjc4yy/8ayEZJcn/2pfEeyHfw58ilFj5wBJoONCRyP6oZVo0TC1N7VS4KKmrt5R2kcJhC8rn5VQc3BEj8FR9O/UJtqe8VmrIKR5LgnVvoLz/8NqSn/xWvi2+fHQGKINvLEn9EBI02u3/R82gyK3vNnll2/j6/wBoNPzqh10cYAgCrji73ye/dtoFz5QsVLJEW/DKoMOJetsw5ngYSeezrYQpgEgbdfzCfDDwaydtHzc98dSRQ8HhUeEeAWLfr9ilTKsb6RVcBH/zO6CK5TS25S5oCDpUQadvAD/iMC1g/tEwb+otsz8RPc+tGYmIQxvILbTemk2aqEOQMwp3ZCx4uTK8Pdb4lR5EHhPiVbrXssGiqbjLqJiisWUl93gldGnzETFYsvHTdbE+Fxk9dR+OS5mID66l6ymweswFeDp3BTSaYyiVUqQ0WoEadh4O7qw/xoM/L24yO6lsG3juzd0qGZ/ipc1tFA8LktZcYvqbIV6XwmEaWIGYDcqOYW+o/yL25JOhcYAmSEK+ZlIw2bq+Yz1jtFIVRb2WFungegML9s2uPq8P0mLUbQjprU77TVwA+2UABdEUaU22uH9i99vO6q1igasXWQMk+c36d+XXByWyvY8HA7gmAc4gJjRfqsNxQEAAAAAAAAAA2YKQi4X9PGvxxoG/jsqLyM0PPhujixtDSQc3sqq+T0EOQO4Yx/Y3cVV0CWiclmyPgKhqcjPTyy1loQgHVILAw==" + }, + { + "header": { + "sequence": 4, + "previousBlockHash": "5075B3E482CCD45B148F2383BCFE2B29275ABCDC333A8184AA1D560D9C34DF9B", + "noteCommitment": { + "type": "Buffer", + "data": "base64:+VA2cx+PWHiMpLzEFW9Wc2hSZHSm3v6y4KkgmXK9BD0=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:9bB6MnIRrGW1Cy5yOMFQG4S5Bq+w0c9POmaYQmBn/Zw=" + }, + "target": "878277375889837647326843029495509009809390053592540685978895509768758568", + "randomness": "0", + "timestamp": 1675701606982, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 10, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAmDKPiPuj1KT9L1XzwyTWH+oMRmuTkBwWEU5RzuwxaKSErKb1Ow6QW/tN147ho+1iIk7c+rGOygC9Y0gSJZjj8iMKnHDbmVaQuhyXegvMFteto76+d7emfVG15MsxuMf+L52WKjFXYEmXLCzXOrJxDqJ7E1YxZJdXuU+HZkaeMecKATLHSJw+TEEV9a02J5VdFYsA2WlR5IC/AHC0+81BeJbFtFMaP5FFwvoe1LrjpDGlZyU2kCDFf3DtzRWb2WTjOoibpqqzzIumnJngUmbd0cgObkwBBE6zHQNAyFRlz+U2FLtRG2AImpETHSZw9QhvckMmZ/lK3mJZQjfcEwXctjw93+sBH5qEQAVgnpLT7LStrxd1Ug8Yahz9nwJ3EVVUZ0xnviXDXVQR1UxlOr+VXRyHVhbVRfbHyeGbT4Wl/IIXfNiNgwOmR4Wm6bTS/KcBfhUxx5S/lUhRo4rFMb0PfExC0cJrWWJnEO8G5q+hToIzNqojDeiji3By4W5ITrzml7VAnUiZ1yupKYMqnUWUJrKbb+eBPq9cnXKdN6FFRUSp0D/YCW00yB1tbgHr6W/TuqTPx/BycvYQplZgAsH2N8DzXYvBn4YLBQsEqMuDlYhjSfqwiQ2jP0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwCSOke4//cqnlgkRLWmtmvZmEc1HlYY3/tTHeTp7RBctkGeGpQukED6JOllwZnPdVRIl6FeUaCICGXuCDxZ3nAw==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture index b98284d8c8..8b315a182f 100644 --- a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture @@ -4434,5 +4434,187 @@ } ] } + ], + "Accounts connectBlock should save assets from the chain the account does not own": [ + { + "id": "0c41ac1f-8622-4082-a833-d43f3cbb3d41", + "name": "a", + "spendingKey": "5b7079a098e3c21368368c4068e1c55feea174d51470bb3d41be760c345a11ff", + "incomingViewKey": "574455b687021ba1a88fb594cdd57f94d285eec2adbd994fdb173c75473b1201", + "outgoingViewKey": "7101c1f5348f989e8459dfced36d27c3e03e0ba392687dcd319c5bf54ea81163", + "publicAddress": "663a56e1c4e0457f8cfafe386d22a43024415414c59ad510226166830dd2b270" + }, + { + "id": "f174c8b7-37db-42e9-a3db-736aa9b2a603", + "name": "b", + "spendingKey": "d737ec5e2a36d8e0a425536c30e91c02f59a8c6ada39fb259f5e0e29b6da916c", + "incomingViewKey": "5e8f27d04813325adf9c939e03323e57b381f40212e57d7dc601f5ab814d0700", + "outgoingViewKey": "8a8350623821bbee3ee2b3cb89deb1ab49aec8213e4ec983d0d726dc22e9cdf9", + "publicAddress": "afc4bc33e9b60adf62b4159ea98942a25d2b69f09e86431a254b04153877232a" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:N6CrTZiHiq+Ku+Gb/gUeH/9z4w3VhOoeyhWEkn1Rxk4=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:l5EXFp7+HrImhREoBAl8G0k1Jykje0y71VU9eWAv838=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675460838150, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAKPKKgugrvx+fqWYVvPUxT9wl/5wNFxE/Sz+yEZT6c0qYrngBKS8HB//LL5B6uWAgykZlOqNvMh5jlBGur8DiiQBRm/R1ePvftxsxSKWh4CqJd7OE0oieVsEye246a+kSS3PhMKA3yWw7+z+4Vgf3ZoCdSqFQ100+btZOG8p33SkEBjiSx/W+nSEE4+6EtFnFowKFctT+kyaLH1pq3wVPfBvZryGu8B4qU6CoJ9ct9zaKgD/wa0V82W1+z34hNcsQd7D/VgDT8l7recaykxMQehZmBKG8wX7RYG/bsKCVxSVeH5eNCtI+8ipaz2E382jfNZoSEQuK56XSN6FqsLyKsTCwpPibrHLf40OTKP75E0psfmZhEYdRecvcwsOAsZQ6hU20DbRv3oh4gX5TOtmrGdw9dBwpJlVHKue5sENuix2KMJcAzuf03RJLIFicz2iXTGYKVvxKoNCZiYyqCwtopafM8CLMhRZ3y/3aZd3DapPqchwCZRUzBXB/YLtvg+bNZ2tK3B1HvPpzsbVmjTdgfyxHVAST4/nsekHoUIbS4D6UFtA2CiLhPeh8wCAEp0u661s9sW/qGb7ftk9N26aTrthr0ESJdADFja0+glH+aQfzOKYbf6NTB0lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwPON9QKeB4vF6ljbkH+G1lKYWoM4ENvvDGD+E7drPHCPztVAiW0nr1LkiNi/MiFmpmCTewO/KzhP78imuGy/KAw==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "4EAE4580260424EB71E1394DBEECBF71A0C2CE9C8967BA318E1FA60D8BDAB4E1", + "noteCommitment": { + "type": "Buffer", + "data": "base64:NBADDbqMi3ZvcgsCXTieuIct5K84kbpJ/d4xBtyjPRw=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:Usy/G4iQCwTaPhlc5Ucn53Q3Zw5KOn1+e4cz3jJlzfE=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675460841276, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAcQdIjhASzHLX/qX3HFyrmT3jkSLjhx2VpSt4fX5/T/GR94y8W9M4y1W49bTV6WTGhiRbRBNDQdUU2APlr9tQKZ/Qpsv7WOC79mvAgzgpWp+Wq5buXZnWJQbg8VCmpUUd9uPPGEnJYQBGR+kghHGmJnJ2xDHzurtU/nPIMsFbcfwQtDaGlGDcuyfBM1U/HJU46zngzPpPbEAHMx+O735CPWDJT/arYse73MD8JWsMJFijHZcXO1VrLQ/TOrIWv9al0cEtZ81H/VX86cZV4GB8o80fSJBsAPRivW/q/CP2mEWX/0ZUSd9udJGCnQYYbAEvHa9M0SX7l9hoMOMrI0M8TyPWINpj3Vhn3YZhLf1o5EeAoIT7fAWmpFobMGhn3dhTZNwAO1rggX+fE0i0AYBK0+VI10lNBImagMBdtOaAV5vJmfutCLxwmWTXrIaSu/sdRPA5X7sdnchygIK4xe3S5mDYEbPKuRE9vmp3dmYD0A8k8y+/2ZO5Jk7QQ67vq8YBbT61x2mJ2wTVqUHWX9LQDbtr0o8Y+nUVLhagQlNtBmG4ZE1H6JL1yzjaSbTsq+OA7CmPHPnZz22p24W7nB6ZbeJoAnHo9CPME9laGFrxTv8PYgrTXD6R20lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwdE83xs+kekJ1e50zVdFwtUep+BlaJ0+LskR73lafPZ1/BuDE3fqvDU9N0xXsdaqD0uju22pYofZNmqXaf01XCA==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 4, + "previousBlockHash": "D7DD8051946C84A48A4AAE33C6A43C5BF83989E957AFE25E9C99306E95E3FBAD", + "noteCommitment": { + "type": "Buffer", + "data": "base64:5hVyzSHLAHHJTUVRqsptlsZMGCKYa2yIhk10RC7GHwA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:/TOrYEOSem+W2sjxXnh8M7sfG/tzndnTeUgHuuYLs+Y=" + }, + "target": "878277375889837647326843029495509009809390053592540685978895509768758568", + "randomness": "0", + "timestamp": 1675460845374, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 11, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAyPafaAOmQbJvTsLe4sT9OW2uDjL2L4Nk7/xjmDjVQ06i1zq1PamuQ11a0W4VjmoEYdBhAPYYwjh4jzu9oMGRV4B8zXMOp3LDr18ULKLVt3q0nV1Xm7hrPohLt8DJN1WMHi9CO4VZOqeNsYZeh4LI5+vfvkNlyhMXayY9Hqa4/3YRdINTao6Vx6/5FTnlke7mIbCd721SKWwYPSCYyf7sCS/8K+v4gH1cHfADbfZ9wf6WKHUUEnXwoQZRrJ6xfDn7fa2Dd/PiNoTEpEt9Qo5MPN99/DJ06VQblr7ObOg984RSlyWzt6RvbPB8ufmKeWk3ncJFooB83nVGPMFgmEr7H+ofbVb9ABLlv9DnMrOV6pT1O1eo/pbqm6EOeHPLvRUVNlp2G2HHJq5c64ZNBR9IFHwhAC9HAqmG4ZtccrRkh94cdb0uLlDwxc3B4u5GcbdDaayR8XOv3t2ONUcj7EAaBjZVMApjPI6NTzwan7DH11ygy4wbMx58T4OBOzI2Ti7LcZso+MQXigAaU2J5fio+hhaKpkcvEkxKqZ4ttLM9iZGVbrEjg/ADB/5AsE//JVS6AjpXvI5vnnZBfgXZgDo5lzOFwIdbP20FielH7nxd3z0DctzluGIe20lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw2gDI/Bio5Ix2CvGnyzNSKPuzkhNgSrmT/y7uCL7W1dR7LLs3RWoiHKbNGrRYM75cCs0IE9iTYMg0s3mHYMb6BA==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + } + ], + "Accounts getAssetStatus should return the correct status for assets": [ + { + "id": "c8b4e705-d8d0-4aed-ba0d-ebfb9a9408b1", + "name": "test", + "spendingKey": "c21f3c936d2b625e9936af6b9f6073fc528cad41397d508c3c09d76e3276e34c", + "incomingViewKey": "63990a705dc74d870942be0d253be3bc22f9a64fbba9a3d719f46820c6bd0405", + "outgoingViewKey": "9947fbfbe0c0e55e1f45adae8ac50a76a2b7d0923a6161393287d6b53298aa55", + "publicAddress": "6bffdcd6a8aae24a4ee518a6bc39bf5987c73a8768ff7ee36dd0c522da0d062d" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "D179D8B74987D6617267D46F4958554BA0DF02D7E5E6117DB02D6FF38FD0F6DA", + "noteCommitment": { + "type": "Buffer", + "data": "base64:a23Hwpn4sh/qNXpY1bN1xJhRnQCgeWpYMVaF+LsJ4D0=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:yxqyXdMMps0EvRR8rP3GBkdYjiAc3m0pdV9+2b8YSss=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1675717010866, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAQpIjqw781b8aRtAAL0KqHZl8YT9mrvHQugNwt706w0GLXpxtSs1LiKRG4n9Hr3tcebDx2jEXdncVKml7EMmJ507r4VuqK9qQBDP9qXDjraKEm87yssLT7c+jWEdmE/ySvIKtKDFpIqnHaxBEygqCYNJAYUF/fKzDrRVtp0AjxOYMNMiASditmzID+yjrg8iVRF1/13eK7ZIPlsH4gxaWMaHQoEQyY/hHKCgYJrH2k7Cq0QiM+ydMLGRrCq7jmDX4fPM9+XUyGEcxknk/GAXADFN9WPipinbBZB75+8LwHtNAO3nWJb6ByJgmsOcf3q7k55wSd1Ujsd9kyVSzBLWlhs6fjJXeV8Frik1/trr7JW31ugl38AEPJR/YxU2vCCIXTQ9kFTjofSrNLp+TB7VPvBBgzqptcKoEHqKvTvaUuR0jRSwm5r9qfPoRICwmMAQRTKalO77eFpuQV+gr3IhRlTwI21sCnWOvv+vLBVG4Dtc4KUsHB52rF4nfW1hJXU81gK2OlPhQ93AgXjd4GLBCQ3Dg/B/hDZfEm7+vudwIdwKGFgymmGXUEQu/S86LPdYAYkGYqLABy//bLxmyZqTE/zmxbcnyG/IWjCLiFt5ke2pskLJBbPUx90lyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwT95D8J4sX8TieuxGx2WrC6dibQjjqdsgzpeoMgUGyTzyrMHT96gfh6dZ516cJHauZHY1a/sQaPwJV8Pi3aziAA==" + } + ] + }, + { + "type": "Buffer", + "data": "base64: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" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "6D8EDAA4236C2F147796118605E2F3B3B7AD1178314A6C7F0A69AC97F7A0E978", + "noteCommitment": { + "type": "Buffer", + "data": "base64:drO3f6r4WnfQQgDDK8rW/eXoSFFdcu+6S2oNDxC4Hys=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:ofIE1uasApAkGVnKi+2iOS7nSmuvNYgocu+vJ8j5Gog=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1675717014167, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAWd+0g7mYcc8xk2+1bFEb+e7BLMyqrLxItyk+xRFgDVmGdxZKixnDvl1bAj5sFAtmOuv/t7sNo6DW+GlLBN+yvMaQb6cfgb3HQHaD9PQr5mSEwWNlCczEpmumklW6gXSD3Nx0IDuJKtJpVRiLVt10rY9oNKUdHeO0VjpQxFmmEkgYqDg7SNpofJofiGQfciQW+Vds6N2LzpvUwv/3YaTXN2GTlrizcYTQRrBrIy8Mo9604JW3THFdNmcoAdwjVBlnm603WpnIRlVBysYYCL//Z4Sf3NmiHT83zUCDLY3GVGcnNnm00ZJqCPNeEWc71Py/QhzgppWj+q7MvOBaBS71IEez9Y4Ua8iV+ENd9qRGHV6SMcz8SMMBfED0HHopuOshmfTmtEBCjaoeAlPLV6UYDoM+5LkDOY1JHDv/d3+p1gr76k8kBJEp5Ako2fKwYnS6r340YPRoDC10mpflr04jSVtMod8YYfV88JGUr07R8EUSylv3p1I2uDdSWtHsG9LS7tQ40eYYciXgHCFV2djB2WlyzgbrRE3iH+bkzodzE/A/ubCDjKAoSNW6OEuggLKWpo8Y7cagafI4gKBul+Q7qitHWs7f7lL9rueX+jDTC01ojDFaDu0YaUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwjeCqJv6E6ymbuOwZq9jqZVzFBEB4oKqIJukMQQM1PURghapsZ7ILT5rTWQdb0z+xUvgtrnq7BzHvTdrXLn/jCQ==" + }, + { + "type": "Buffer", + "data": "base64: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" + } + ] + } ] } \ No newline at end of file diff --git a/ironfish/src/wallet/account.test.ts b/ironfish/src/wallet/account.test.ts index c18e8be32f..8a5581185d 100644 --- a/ironfish/src/wallet/account.test.ts +++ b/ironfish/src/wallet/account.test.ts @@ -9,6 +9,7 @@ import { useAccountFixture, useBlockWithTx, useBlockWithTxs, + useBurnBlockFixture, useMinerBlockFixture, useMintBlockFixture, useTxFixture, @@ -542,6 +543,144 @@ describe('Accounts', () => { expect(connectedRecord.sequence).toEqual(block3.header.sequence) expect(connectedRecord.timestamp).toEqual(pendingRecord.timestamp) }) + + it('should correctly update the asset store from a mint description', async () => { + const { node } = nodeTest + + const accountA = await useAccountFixture(node.wallet, 'accountA') + const accountB = await useAccountFixture(node.wallet, 'accountB') + + const block2 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) + await node.chain.addBlock(block2) + await node.wallet.updateHead() + + const asset = new Asset(accountA.spendingKey, 'mint-asset', 'metadata') + const value = BigInt(10) + const mintBlock = await useMintBlockFixture({ + node, + account: accountA, + asset, + value, + sequence: 3, + }) + await expect(node.chain).toAddBlock(mintBlock) + await node.wallet.updateHead() + + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toEqual({ + blockHash: mintBlock.header.hash, + createdTransactionHash: mintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: mintBlock.header.sequence, + supply: value, + }) + + expect(await accountB['walletDb'].getAsset(accountB, asset.id())).toBeUndefined() + }) + + it('should overwrite pending asset fields from a connected mint description', async () => { + const { node } = nodeTest + const account = await useAccountFixture(node.wallet) + const asset = new Asset(account.spendingKey, 'testcoin', 'metadata') + + const minerBlock = await useMinerBlockFixture(node.chain, undefined, account, node.wallet) + await node.chain.addBlock(minerBlock) + await node.wallet.updateHead() + + const firstMintValue = BigInt(10) + const firstMintBlock = await useMintBlockFixture({ + node, + account, + asset, + value: firstMintValue, + sequence: 3, + }) + const firstMintTransaction = firstMintBlock.transactions[1] + + // Verify block fields are empty since this has not been connected yet + expect(await account['walletDb'].getAsset(account, asset.id())).toEqual({ + blockHash: null, + createdTransactionHash: firstMintTransaction.hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: null, + supply: null, + }) + + const secondMintValue = BigInt(42) + const secondMintBlock = await useMintBlockFixture({ + node, + account, + asset, + value: secondMintValue, + sequence: 3, + }) + const secondMintTransaction = secondMintBlock.transactions[1] + await expect(node.chain).toAddBlock(secondMintBlock) + await node.wallet.updateHead() + + // Verify block fields are for the second block since that was connected + expect(await account['walletDb'].getAsset(account, asset.id())).toEqual({ + blockHash: secondMintBlock.header.hash, + createdTransactionHash: secondMintTransaction.hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: secondMintBlock.header.sequence, + supply: secondMintValue, + }) + }) + + it('should correctly update the asset store from a burn description', async () => { + const { node } = nodeTest + + const accountA = await useAccountFixture(node.wallet, 'accountA') + const accountB = await useAccountFixture(node.wallet, 'accountB') + + const block2 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) + await node.chain.addBlock(block2) + await node.wallet.updateHead() + + const asset = new Asset(accountA.spendingKey, 'mint-asset', 'metadata') + const mintValue = BigInt(10) + const mintBlock = await useMintBlockFixture({ + node, + account: accountA, + asset, + value: mintValue, + sequence: 3, + }) + await expect(node.chain).toAddBlock(mintBlock) + await node.wallet.updateHead() + + const burnValue = BigInt(1) + const burnBlock = await useBurnBlockFixture({ + node, + account: accountA, + asset, + value: burnValue, + sequence: 4, + }) + await expect(node.chain).toAddBlock(burnBlock) + await node.wallet.updateHead() + + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toEqual({ + blockHash: mintBlock.header.hash, + createdTransactionHash: mintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: mintBlock.header.sequence, + supply: mintValue - burnValue, + }) + expect(await accountB['walletDb'].getAsset(accountB, asset.id())).toBeUndefined() + }) }) describe('disconnectTransaction', () => { @@ -715,6 +854,143 @@ describe('Accounts', () => { expect(sequenceIndexEntry).toBeUndefined() }) + + it('should correctly update the asset store from a mint description', async () => { + const { node } = nodeTest + + const accountA = await useAccountFixture(node.wallet, 'accountA') + const accountB = await useAccountFixture(node.wallet, 'accountB') + + const block2 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) + await node.chain.addBlock(block2) + await node.wallet.updateHead() + + const asset = new Asset(accountA.spendingKey, 'mint-asset', 'metadata') + const firstMintValue = BigInt(10) + const firstMintBlock = await useMintBlockFixture({ + node, + account: accountA, + asset, + value: firstMintValue, + sequence: 3, + }) + await expect(node.chain).toAddBlock(firstMintBlock) + await node.wallet.updateHead() + + const secondMintValue = BigInt(10) + const secondMintBlock = await useMintBlockFixture({ + node, + account: accountA, + asset, + value: secondMintValue, + sequence: 4, + }) + await expect(node.chain).toAddBlock(secondMintBlock) + await node.wallet.updateHead() + + // Check the aggregate from both mints + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toEqual({ + blockHash: firstMintBlock.header.hash, + createdTransactionHash: firstMintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: firstMintBlock.header.sequence, + supply: firstMintValue + secondMintValue, + }) + + await accountA.disconnectTransaction( + secondMintBlock.header, + secondMintBlock.transactions[1], + ) + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toEqual({ + blockHash: firstMintBlock.header.hash, + createdTransactionHash: firstMintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: firstMintBlock.header.sequence, + supply: firstMintValue, + }) + expect(await accountB['walletDb'].getAsset(accountB, asset.id())).toBeUndefined() + + await accountA.disconnectTransaction( + firstMintBlock.header, + firstMintBlock.transactions[1], + ) + // Verify the block fields are null after a disconnect + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toEqual({ + blockHash: null, + createdTransactionHash: firstMintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence: null, + supply: null, + }) + + // Expiration of the first mint will delete the record + await accountA.expireTransaction(firstMintBlock.transactions[1]) + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toBeUndefined() + expect(await accountB['walletDb'].getAsset(accountB, asset.id())).toBeUndefined() + }) + + it('should correctly update the asset store from a burn description', async () => { + const { node } = nodeTest + + const accountA = await useAccountFixture(node.wallet, 'accountA') + const accountB = await useAccountFixture(node.wallet, 'accountB') + + const block2 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) + await node.chain.addBlock(block2) + await node.wallet.updateHead() + + const asset = new Asset(accountA.spendingKey, 'mint-asset', 'metadata') + const mintValue = BigInt(10) + const mintBlock = await useMintBlockFixture({ + node, + account: accountA, + asset, + value: mintValue, + sequence: 3, + }) + await expect(node.chain).toAddBlock(mintBlock) + await node.wallet.updateHead() + + const burnValue = BigInt(1) + const burnBlock = await useBurnBlockFixture({ + node, + account: accountA, + asset, + value: burnValue, + sequence: 4, + }) + await expect(node.chain).toAddBlock(burnBlock) + await node.wallet.updateHead() + + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toMatchObject({ + createdTransactionHash: mintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + supply: mintValue - burnValue, + }) + + await accountA.disconnectTransaction(burnBlock.header, burnBlock.transactions[1]) + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toMatchObject({ + createdTransactionHash: mintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + supply: mintValue, + }) + expect(await accountB['walletDb'].getAsset(accountB, asset.id())).toBeUndefined() + }) }) describe('deleteTransaction', () => { diff --git a/ironfish/src/wallet/account.ts b/ironfish/src/wallet/account.ts index 77c98c574c..2dd57d3750 100644 --- a/ironfish/src/wallet/account.ts +++ b/ironfish/src/wallet/account.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 { Asset } from '@ironfish/rust-nodejs' import { BufferMap } from 'buffer-map' import MurmurHash3 from 'imurmurhash' import { Assert } from '../assert' @@ -12,6 +13,7 @@ import { StorageUtils } from '../storage/database/utils' import { DecryptedNote } from '../workerPool/tasks/decryptNotes' import { AssetBalances } from './assetBalances' import { AccountValue } from './walletdb/accountValue' +import { AssetValue } from './walletdb/assetValue' import { BalanceValue } from './walletdb/balanceValue' import { DecryptedNoteValue } from './walletdb/decryptedNoteValue' import { HeadValue } from './walletdb/headValue' @@ -140,7 +142,7 @@ export class Account { let timestamp = blockHeader.timestamp await this.walletDb.db.withTransaction(tx, async (tx) => { - const transactionValue = await this.getTransaction(transaction.hash(), tx) + let transactionValue = await this.getTransaction(transaction.hash(), tx) if (transactionValue) { submittedSequence = transactionValue.submittedSequence timestamp = transactionValue.timestamp @@ -185,22 +187,250 @@ export class Account { await this.walletDb.saveDecryptedNote(this, spentNoteHash, spentNote, tx) } - await this.walletDb.saveTransaction( + transactionValue = { + transaction, + blockHash, + sequence, + submittedSequence, + timestamp, + assetBalanceDeltas, + } + + await this.saveMintsToAssetsStore(transactionValue, tx) + await this.saveConnectedBurnsToAssetsStore(transactionValue.transaction, tx) + + await this.walletDb.saveTransaction(this, transaction.hash(), transactionValue, tx) + }) + + return assetBalanceDeltas + } + + async saveAssetFromChain( + createdTransactionHash: Buffer, + id: Buffer, + metadata: Buffer, + name: Buffer, + owner: Buffer, + blockHeader?: { hash: Buffer | null; sequence: number | null }, + tx?: IDatabaseTransaction, + ): Promise { + if (id.equals(Asset.nativeId())) { + return + } + + await this.walletDb.putAsset( + this, + id, + { + blockHash: blockHeader?.hash ?? null, + createdTransactionHash, + id, + metadata, + name, + owner, + sequence: blockHeader?.sequence ?? null, + supply: null, + }, + tx, + ) + } + + async updateAssetWithBlockHeader( + assetValue: AssetValue, + blockHeader: { hash: Buffer; sequence: number }, + tx?: IDatabaseTransaction, + ): Promise { + // Don't update for the native asset or if previously confirmed + if (assetValue.id.equals(Asset.nativeId()) || assetValue.blockHash) { + return + } + + await this.walletDb.putAsset( + this, + assetValue.id, + { + blockHash: blockHeader.hash, + createdTransactionHash: assetValue.createdTransactionHash, + id: assetValue.id, + metadata: assetValue.metadata, + name: assetValue.name, + owner: assetValue.owner, + sequence: blockHeader.sequence, + supply: assetValue.supply, + }, + tx, + ) + } + + async saveMintsToAssetsStore( + { blockHash, sequence, transaction }: TransactionValue, + tx?: IDatabaseTransaction, + ): Promise { + for (const { asset, value } of transaction.mints) { + // Only store the asset for the owner + if (asset.owner().toString('hex') !== this.publicAddress) { + continue + } + + const existingAsset = await this.walletDb.getAsset(this, asset.id(), tx) + + let createdTransactionHash = transaction.hash() + let supply = BigInt(0) + + // Adjust supply if this transaction is connected on a block. + if (blockHash && sequence) { + supply += value + } + + // If the asset has been previously confirmed on a block, use the existing + // block hash, created transaction hash, and sequence for the database + // upsert. Adjust supply from the current record. + if (existingAsset && existingAsset.blockHash && existingAsset.sequence) { + Assert.isNotNull(existingAsset.supply, 'Supply should be non-null for asset') + blockHash = existingAsset.blockHash + createdTransactionHash = existingAsset.createdTransactionHash + sequence = existingAsset.sequence + supply += existingAsset.supply + } + + await this.walletDb.putAsset( this, - transaction.hash(), + asset.id(), { - transaction, blockHash, + createdTransactionHash, + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), sequence, - submittedSequence, - timestamp, - assetBalanceDeltas, + supply, }, tx, ) - }) + } + } - return assetBalanceDeltas + async saveConnectedBurnsToAssetsStore( + transaction: Transaction, + tx?: IDatabaseTransaction, + ): Promise { + for (const { assetId, value } of transaction.burns) { + const existingAsset = await this.walletDb.getAsset(this, assetId, tx) + if (!existingAsset) { + continue + } + // Verify the owner matches before processing a burn + Assert.isEqual( + existingAsset.owner.toString('hex'), + this.publicAddress, + 'Existing asset owner should match public address', + ) + Assert.isNotNull(existingAsset.supply, 'Supply should be non-null for asset') + + const supply = existingAsset.supply - value + Assert.isTrue(supply >= BigInt(0), 'Invalid burn value') + + await this.walletDb.putAsset( + this, + assetId, + { + blockHash: existingAsset.blockHash, + createdTransactionHash: existingAsset.createdTransactionHash, + id: existingAsset.id, + metadata: existingAsset.metadata, + name: existingAsset.name, + owner: existingAsset.owner, + sequence: existingAsset.sequence, + supply, + }, + tx, + ) + } + } + + private async deleteDisconnectedBurnsFromAssetsStore( + transaction: Transaction, + tx: IDatabaseTransaction, + ): Promise { + for (const { assetId, value } of transaction.burns.slice().reverse()) { + const existingAsset = await this.walletDb.getAsset(this, assetId, tx) + if (!existingAsset) { + continue + } + // Verify the owner matches before processing a burn + Assert.isEqual( + existingAsset.owner.toString('hex'), + this.publicAddress, + 'Existing asset owner should match public address', + ) + Assert.isNotNull(existingAsset.supply, 'Supply should be non-null for asset') + + const existingSupply = existingAsset.supply + const supply = existingSupply + value + + await this.walletDb.putAsset( + this, + assetId, + { + blockHash: existingAsset.blockHash, + createdTransactionHash: existingAsset.createdTransactionHash, + id: existingAsset.id, + metadata: existingAsset.metadata, + name: existingAsset.name, + owner: existingAsset.owner, + sequence: existingAsset.sequence, + supply, + }, + tx, + ) + } + } + + private async deleteDisconnectedMintsFromAssetsStore( + blockHeader: BlockHeader, + transaction: Transaction, + tx: IDatabaseTransaction, + ): Promise { + for (const { asset, value } of transaction.mints.slice().reverse()) { + // Only update the mint for the owner + if (asset.owner().toString('hex') !== this.publicAddress) { + continue + } + + const existingAsset = await this.walletDb.getAsset(this, asset.id(), tx) + Assert.isNotUndefined(existingAsset) + Assert.isNotNull(existingAsset.supply, 'Supply should be non-null for asset') + + const existingSupply = existingAsset.supply + const supply = existingSupply - value + Assert.isTrue(supply >= BigInt(0)) + + let blockHash = existingAsset.blockHash + let sequence = existingAsset.sequence + // Mark this asset as pending if the block hash matches the hash on the + // disconnected header + if (blockHash && blockHash.equals(blockHeader.hash)) { + blockHash = null + sequence = null + } + + await this.walletDb.putAsset( + this, + asset.id(), + { + blockHash, + createdTransactionHash: existingAsset.createdTransactionHash, + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: asset.owner(), + sequence, + supply, + }, + tx, + ) + } } async addPendingTransaction( @@ -253,19 +483,18 @@ export class Account { await this.walletDb.saveDecryptedNote(this, spentNoteHash, spentNote, tx) } - await this.walletDb.saveTransaction( - this, - transaction.hash(), - { - transaction, - blockHash: null, - sequence: null, - submittedSequence, - timestamp: new Date(), - assetBalanceDeltas, - }, - tx, - ) + const transactionValue = { + transaction, + blockHash: null, + sequence: null, + submittedSequence, + timestamp: new Date(), + assetBalanceDeltas, + } + + await this.saveMintsToAssetsStore(transactionValue, tx) + + await this.walletDb.saveTransaction(this, transaction.hash(), transactionValue, tx) }) } @@ -328,6 +557,8 @@ export class Account { assetBalanceDeltas.increment(spentNote.note.assetId(), spentNote.note.value()) } + await this.deleteDisconnectedBurnsFromAssetsStore(transaction, tx) + await this.deleteDisconnectedMintsFromAssetsStore(blockHeader, transaction, tx) await this.walletDb.deleteSequenceToTransactionHash( this, blockHeader.sequence, @@ -396,6 +627,13 @@ export class Account { return await this.walletDb.loadTransaction(this, hash, tx) } + async getAsset( + id: Buffer, + tx?: IDatabaseTransaction, + ): Promise | undefined> { + return this.walletDb.getAsset(this, id, tx) + } + async hasTransaction(hash: Buffer, tx?: IDatabaseTransaction): Promise { return this.walletDb.hasTransaction(this, hash, tx) } @@ -422,6 +660,16 @@ export class Account { return this.walletDb.loadTransactionsByTime(this, tx) } + async *getTransactionsOrderedBySequence( + tx?: IDatabaseTransaction, + ): AsyncGenerator> { + for await (const { hash } of this.walletDb.getTransactionHashesBySequence(this, tx)) { + const transaction = await this.getTransaction(hash, tx) + Assert.isNotUndefined(transaction) + yield transaction + } + } + getPendingTransactions( headSequence: number, tx?: IDatabaseTransaction, @@ -466,6 +714,7 @@ export class Account { } } + await this.deleteCreatedAssetsFromTransaction(transaction, tx) await this.walletDb.deletePendingTransactionHash( this, transaction.expiration(), @@ -475,6 +724,31 @@ export class Account { }) } + private async deleteCreatedAssetsFromTransaction( + transaction: Transaction, + tx?: IDatabaseTransaction, + ): Promise { + for (const { asset } of transaction.mints.slice().reverse()) { + // Only update the mint for the owner + if (asset.owner().toString('hex') !== this.publicAddress) { + continue + } + + const existingAsset = await this.walletDb.getAsset(this, asset.id(), tx) + Assert.isNotUndefined(existingAsset) + + // If we are reverting the transaction which matches the created at + // hash of the asset, delete the record from the store + if (transaction.hash().equals(existingAsset.createdTransactionHash)) { + await this.walletDb.deleteAsset(this, asset.id(), tx) + } + } + } + + getAssets(tx?: IDatabaseTransaction): AsyncGenerator> { + return this.walletDb.loadAssets(this, tx) + } + async *getBalances( confirmations: number, tx?: IDatabaseTransaction, diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index 3862067316..962098c5c3 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -19,6 +19,7 @@ import { } from '../testUtilities' import { AsyncUtils } from '../utils' import { TransactionStatus, TransactionType } from '../wallet' +import { AssetStatus } from './wallet' describe('Accounts', () => { const nodeTest = createNodeTest() @@ -411,7 +412,7 @@ describe('Accounts', () => { unconfirmed: BigInt(10), unconfirmedCount: 0, }) - }, 100000) + }) }) describe('getBalance', () => { @@ -788,7 +789,7 @@ describe('Accounts', () => { }, ) - expect(rawTransaction.receives.length).toBe(1) + expect(rawTransaction.outputs.length).toBe(1) expect(rawTransaction.expiration).toBeDefined() expect(rawTransaction.burns.length).toBe(0) expect(rawTransaction.mints.length).toBe(0) @@ -1605,6 +1606,69 @@ describe('Accounts', () => { }) }) + it('should save assets from the chain the account does not own', async () => { + const { node } = await nodeTest.createSetup() + + const accountA = await useAccountFixture(node.wallet, 'a') + const accountB = await useAccountFixture(node.wallet, 'b') + + const minerBlock = await useMinerBlockFixture( + node.chain, + undefined, + accountA, + node.wallet, + ) + await expect(node.chain).toAddBlock(minerBlock) + await node.wallet.updateHead() + + const asset = new Asset(accountA.spendingKey, 'fakeasset', 'metadata') + const value = BigInt(10) + const mintBlock = await useMintBlockFixture({ + node, + account: accountA, + asset, + value, + sequence: 3, + }) + await expect(node.chain).toAddBlock(mintBlock) + await node.wallet.updateHead() + + const transaction = await usePostTxFixture({ + node, + wallet: node.wallet, + from: accountA, + to: accountB, + assetId: asset.id(), + amount: BigInt(1n), + }) + const block = await useMinerBlockFixture(node.chain, undefined, undefined, undefined, [ + transaction, + ]) + await expect(node.chain).toAddBlock(block) + await node.wallet.updateHead() + + expect(await accountA['walletDb'].getAsset(accountA, asset.id())).toEqual({ + blockHash: mintBlock.header.hash, + createdTransactionHash: mintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: Buffer.from(accountA.publicAddress, 'hex'), + sequence: mintBlock.header.sequence, + supply: value, + }) + expect(await accountB['walletDb'].getAsset(accountB, asset.id())).toEqual({ + blockHash: block.header.hash, + createdTransactionHash: mintBlock.transactions[1].hash(), + id: asset.id(), + metadata: asset.metadata(), + name: asset.name(), + owner: Buffer.from(accountA.publicAddress, 'hex'), + sequence: block.header.sequence, + supply: null, + }) + }) + it('should add transactions to accounts if the account spends, but does not receive notes', async () => { const { node } = await nodeTest.createSetup() const { node: node2 } = await nodeTest.createSetup() @@ -1649,6 +1713,48 @@ describe('Accounts', () => { }) }) + describe('getAssetStatus', () => { + it('should return the correct status for assets', async () => { + const { node } = await nodeTest.createSetup() + const account = await useAccountFixture(node.wallet) + + const mined = await useMinerBlockFixture(node.chain, 2, account) + await expect(node.chain).toAddBlock(mined) + await node.wallet.updateHead() + + const asset = new Asset(account.spendingKey, 'asset', 'metadata') + const value = BigInt(10) + const mintBlock = await useMintBlockFixture({ + node, + account, + asset, + value, + }) + + let assetValue = await node.wallet.walletDb.getAsset(account, asset.id()) + Assert.isNotUndefined(assetValue) + + // Check status before added to a block + expect(await node.wallet.getAssetStatus(account, assetValue)).toEqual(AssetStatus.PENDING) + + // Add to a block and check different confirmation ranges + await expect(node.chain).toAddBlock(mintBlock) + await node.wallet.updateHead() + assetValue = await node.wallet.walletDb.getAsset(account, asset.id()) + Assert.isNotUndefined(assetValue) + expect(await node.wallet.getAssetStatus(account, assetValue)).toEqual( + AssetStatus.CONFIRMED, + ) + expect( + await node.wallet.getAssetStatus(account, assetValue, { confirmations: 2 }), + ).toEqual(AssetStatus.UNCONFIRMED) + + // Remove the head and check status + jest.spyOn(account, 'getHead').mockResolvedValueOnce(Promise.resolve(null)) + expect(await node.wallet.getAssetStatus(account, assetValue)).toEqual(AssetStatus.UNKNOWN) + }) + }) + describe('disconnectBlock', () => { it('should update transactions in the walletDb with blockHash and sequence null', async () => { const { node } = await nodeTest.createSetup() diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index 6e15b2d3b1..6fd7741623 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -46,12 +46,20 @@ import { NotEnoughFundsError } from './errors' import { MintAssetOptions } from './interfaces/mintAssetOptions' import { validateAccount } from './validator' import { AccountValue } from './walletdb/accountValue' +import { AssetValue } from './walletdb/assetValue' import { DecryptedNoteValue } from './walletdb/decryptedNoteValue' import { TransactionValue } from './walletdb/transactionValue' import { WalletDB } from './walletdb/walletdb' const noteHasher = new NoteHasher() +export enum AssetStatus { + CONFIRMED = 'confirmed', + PENDING = 'pending', + UNCONFIRMED = 'unconfirmed', + UNKNOWN = 'unknown', +} + export enum TransactionStatus { CONFIRMED = 'confirmed', EXPIRED = 'expired', @@ -413,6 +421,7 @@ export class Wallet { assetBalanceDeltas.update(transactionDeltas) + await this.upsertAssetsFromDecryptedNotes(account, decryptedNotes, blockHeader, tx) scan?.signal(blockHeader.sequence) } @@ -428,6 +437,38 @@ export class Wallet { } } + private async upsertAssetsFromDecryptedNotes( + account: Account, + decryptedNotes: DecryptedNote[], + blockHeader?: BlockHeader, + tx?: IDatabaseTransaction, + ): Promise { + for (const { serializedNote } of decryptedNotes) { + const note = new Note(serializedNote) + const asset = await this.walletDb.getAsset(account, note.assetId(), tx) + + if (!asset) { + const chainAsset = await this.chain.getAssetById(note.assetId()) + Assert.isNotNull(chainAsset, 'Asset must be non-null in the chain') + await account.saveAssetFromChain( + chainAsset.createdTransactionHash, + chainAsset.id, + chainAsset.metadata, + chainAsset.name, + chainAsset.owner, + blockHeader, + tx, + ) + } else if (blockHeader) { + await account.updateAssetWithBlockHeader( + asset, + { hash: blockHeader.hash, sequence: blockHeader.sequence }, + tx, + ) + } + } + } + async disconnectBlock(header: BlockHeader): Promise { const accounts = await AsyncUtils.filter(this.listAccounts(), async (account) => { const accountHead = await account.getHead() @@ -491,6 +532,7 @@ export class Wallet { } await account.addPendingTransaction(transaction, decryptedNotes, this.chain.head.sequence) + await this.upsertAssetsFromDecryptedNotes(account, decryptedNotes) } } @@ -631,7 +673,7 @@ export class Wallet { async send( memPool: MemPool, sender: Account, - receives: { + outputs: { publicAddress: string amount: bigint memo: string @@ -642,7 +684,7 @@ export class Wallet { expiration?: number | null, confirmations?: number | null, ): Promise { - const raw = await this.createTransaction(sender, receives, [], [], { + const raw = await this.createTransaction(sender, outputs, [], [], { fee, expirationDelta, expiration: expiration ?? undefined, @@ -712,7 +754,7 @@ export class Wallet { async createTransaction( sender: Account, - receives: { + outputs: { publicAddress: string amount: bigint memo: string @@ -762,16 +804,16 @@ export class Wallet { raw.mints = mints raw.burns = burns - for (const receive of receives) { + for (const output of outputs) { const note = new NativeNote( - receive.publicAddress, - receive.amount, - receive.memo, - receive.assetId, + output.publicAddress, + output.amount, + output.memo, + output.assetId, sender.publicAddress, ) - raw.receives.push({ note: new Note(note.serialize()) }) + raw.outputs.push({ note: new Note(note.serialize()) }) } if (options.fee) { @@ -786,7 +828,7 @@ export class Wallet { size += 4 // expiration size += 64 // signature - size += raw.receives.length * NOTE_ENCRYPTED_SERIALIZED_SIZE_IN_BYTE + size += raw.outputs.length * NOTE_ENCRYPTED_SERIALIZED_SIZE_IN_BYTE size += raw.mints.length * (ASSET_LENGTH + 8) @@ -877,9 +919,9 @@ export class Wallet { const amountsNeeded = new BufferMap() amountsNeeded.set(Asset.nativeId(), options.fee) - for (const receive of raw.receives) { - const currentAmount = amountsNeeded.get(receive.note.assetId()) ?? BigInt(0) - amountsNeeded.set(receive.note.assetId(), currentAmount + receive.note.value()) + for (const output of raw.outputs) { + const currentAmount = amountsNeeded.get(output.note.assetId()) ?? BigInt(0) + amountsNeeded.set(output.note.assetId(), currentAmount + output.note.value()) } for (const burn of raw.burns) { @@ -1162,6 +1204,29 @@ export class Wallet { } } + async getAssetStatus( + account: Account, + assetValue: AssetValue, + options?: { + headSequence?: number | null + confirmations?: number + }, + ): Promise { + const confirmations = options?.confirmations ?? this.config.get('confirmations') + + const headSequence = options?.headSequence ?? (await account.getHead())?.sequence + if (!headSequence) { + return AssetStatus.UNKNOWN + } + + if (assetValue.sequence) { + const confirmed = headSequence - assetValue.sequence >= confirmations + return confirmed ? AssetStatus.CONFIRMED : AssetStatus.UNCONFIRMED + } + + return AssetStatus.PENDING + } + async getTransactionType( account: Account, transaction: TransactionValue, diff --git a/ironfish/src/wallet/walletdb/assetValue.test.ts b/ironfish/src/wallet/walletdb/assetValue.test.ts index 51a08bbe1f..fca0226b78 100644 --- a/ironfish/src/wallet/walletdb/assetValue.test.ts +++ b/ironfish/src/wallet/walletdb/assetValue.test.ts @@ -14,11 +14,13 @@ describe('AssetValueEncoding', () => { const encoder = new AssetValueEncoding() const value: AssetValue = { + blockHash: null, createdTransactionHash: Buffer.alloc(32, 0), id: asset.id(), metadata: asset.metadata(), name: asset.name(), owner: asset.owner(), + sequence: null, supply: BigInt(100), } const buffer = encoder.serialize(value) diff --git a/ironfish/src/wallet/walletdb/assetValue.ts b/ironfish/src/wallet/walletdb/assetValue.ts index a2c85b2e9a..9d4e46fc39 100644 --- a/ironfish/src/wallet/walletdb/assetValue.ts +++ b/ironfish/src/wallet/walletdb/assetValue.ts @@ -18,40 +18,95 @@ export interface AssetValue { metadata: Buffer name: Buffer owner: Buffer - supply: bigint + // Populated for assets the account owns + supply: bigint | null + // Populated once the asset has been added to the main chain + blockHash: Buffer | null + sequence: number | null } export class AssetValueEncoding implements IDatabaseEncoding { serialize(value: AssetValue): Buffer { const bw = bufio.write(this.getSize(value)) + + let flags = 0 + flags |= Number(!!value.blockHash) << 0 + flags |= Number(!!value.sequence) << 1 + flags |= Number(!!value.supply) << 2 + bw.writeU8(flags) + + if (value.blockHash) { + bw.writeHash(value.blockHash) + } + + if (value.sequence) { + bw.writeU32(value.sequence) + } + + if (value.supply) { + bw.writeVarBytes(BigIntUtils.toBytesLE(value.supply)) + } + bw.writeHash(value.createdTransactionHash) bw.writeHash(value.id) bw.writeBytes(value.metadata) bw.writeBytes(value.name) bw.writeBytes(value.owner) - bw.writeVarBytes(BigIntUtils.toBytesLE(value.supply)) return bw.render() } deserialize(buffer: Buffer): AssetValue { const reader = bufio.read(buffer, true) + + const flags = reader.readU8() + const hasBlockHash = flags & (1 << 0) + const hasSequence = flags & (1 << 1) + const hasSupply = flags & (1 << 2) + + let blockHash = null + if (hasBlockHash) { + blockHash = reader.readHash() + } + + let sequence = null + if (hasSequence) { + sequence = reader.readU32() + } + + let supply = null + if (hasSupply) { + supply = BigIntUtils.fromBytesLE(reader.readVarBytes()) + } + const createdTransactionHash = reader.readHash() const id = reader.readBytes(ASSET_ID_LENGTH) const metadata = reader.readBytes(ASSET_METADATA_LENGTH) const name = reader.readBytes(ASSET_NAME_LENGTH) const owner = reader.readBytes(ASSET_OWNER_LENGTH) - const supply = BigIntUtils.fromBytesLE(reader.readVarBytes()) - return { createdTransactionHash, id, metadata, name, owner, supply } + return { blockHash, createdTransactionHash, id, metadata, name, owner, sequence, supply } } getSize(value: AssetValue): number { let size = 0 + size += 1 // flags + + if (value.blockHash) { + size += 32 + } + + if (value.sequence) { + size += 4 + } + + if (value.supply) { + size += bufio.sizeVarBytes(BigIntUtils.toBytesLE(value.supply)) + } + size += 32 // createdTransactionHash size += ASSET_ID_LENGTH // id size += ASSET_METADATA_LENGTH // metadata size += ASSET_NAME_LENGTH // name size += PUBLIC_ADDRESS_LENGTH // owner - size += bufio.sizeVarBytes(BigIntUtils.toBytesLE(value.supply)) // supply return size } } diff --git a/ironfish/src/wallet/walletdb/decryptedNoteValue.ts b/ironfish/src/wallet/walletdb/decryptedNoteValue.ts index d8c4985a71..835edf5308 100644 --- a/ironfish/src/wallet/walletdb/decryptedNoteValue.ts +++ b/ironfish/src/wallet/walletdb/decryptedNoteValue.ts @@ -32,7 +32,7 @@ export class DecryptedNoteValueEncoding implements IDatabaseEncoding ({ defaultAccountId: null, @@ -385,6 +386,19 @@ export class WalletDB { await this.nonChainNoteHashes.clear(tx, account.prefixRange) } + async *getTransactionHashesBySequence( + account: Account, + tx?: IDatabaseTransaction, + ): AsyncGenerator<{ sequence: number; hash: Buffer }> { + for await (const [, [sequence, hash]] of this.sequenceToTransactionHash.getAllKeysIter( + tx, + account.prefixRange, + { ordered: true }, + )) { + yield { sequence, hash } + } + } + async *loadTransactions( account: Account, tx?: IDatabaseTransaction, @@ -936,4 +950,49 @@ export class WalletDB { yield transaction } } + + async putAsset( + account: Account, + assetId: Buffer, + assetValue: AssetValue, + tx?: IDatabaseTransaction, + ): Promise { + await this.assets.put([account.prefix, assetId], assetValue, tx) + } + + async getAsset( + account: Account, + assetId: Buffer, + tx?: IDatabaseTransaction, + ): Promise { + if (assetId.equals(Asset.nativeId())) { + return { + createdTransactionHash: GENESIS_BLOCK_PREVIOUS, + id: Asset.nativeId(), + metadata: Buffer.from('Native asset of Iron Fish blockchain', 'utf8'), + name: Buffer.from('$IRON', 'utf8'), + owner: Buffer.from('Iron Fish', 'utf8'), + blockHash: null, + sequence: null, + supply: null, + } + } + return this.assets.get([account.prefix, assetId], tx) + } + + async *loadAssets(account: Account, tx?: IDatabaseTransaction): AsyncGenerator { + for await (const asset of this.assets.getAllValuesIter(tx, account.prefixRange, { + ordered: true, + })) { + yield asset + } + } + + async deleteAsset( + account: Account, + assetId: Buffer, + tx?: IDatabaseTransaction, + ): Promise { + await this.assets.del([account.prefix, assetId], tx) + } } diff --git a/ironfish/src/workerPool/tasks/createMinersFee.test.ts b/ironfish/src/workerPool/tasks/createMinersFee.test.ts index 872ed2351f..022ca0d10d 100644 --- a/ironfish/src/workerPool/tasks/createMinersFee.test.ts +++ b/ironfish/src/workerPool/tasks/createMinersFee.test.ts @@ -18,7 +18,7 @@ jest.mock('@ironfish/rust-nodejs', () => { ...module, Transaction: jest.fn().mockImplementation(() => ({ post_miners_fee: postMinersFee, - receive: jest.fn(), + output: jest.fn(), })), } }) diff --git a/ironfish/src/workerPool/tasks/createMinersFee.ts b/ironfish/src/workerPool/tasks/createMinersFee.ts index 3aad179e23..e30f46c84b 100644 --- a/ironfish/src/workerPool/tasks/createMinersFee.ts +++ b/ironfish/src/workerPool/tasks/createMinersFee.ts @@ -87,7 +87,7 @@ export class CreateMinersFeeTask extends WorkerTask { ) const transaction = new Transaction(spendKey) - transaction.receive(minerNote) + transaction.output(minerNote) const serializedTransactionPosted = transaction.post_miners_fee() return new CreateMinersFeeResponse(serializedTransactionPosted, jobId) diff --git a/ironfish/src/workerPool/tasks/sleep.ts b/ironfish/src/workerPool/tasks/sleep.ts index c4631e1d24..e1ede9959f 100644 --- a/ironfish/src/workerPool/tasks/sleep.ts +++ b/ironfish/src/workerPool/tasks/sleep.ts @@ -21,14 +21,14 @@ export class SleepRequest extends WorkerMessage { serialize(): Buffer { const bw = bufio.write(this.getSize()) bw.writeDouble(this.sleep) - bw.writeVarString(this.error) + bw.writeVarString(this.error, 'utf8') return bw.render() } static deserialize(jobId: number, buffer: Buffer): SleepRequest { const reader = bufio.read(buffer, true) const sleep = reader.readDouble() - const error = reader.readVarString() + const error = reader.readVarString('utf8') return new SleepRequest(sleep, error, jobId) } diff --git a/ironfish/src/workerPool/tasks/submitTelemetry.ts b/ironfish/src/workerPool/tasks/submitTelemetry.ts index 9d806ad6c9..9bacc7c1dc 100644 --- a/ironfish/src/workerPool/tasks/submitTelemetry.ts +++ b/ironfish/src/workerPool/tasks/submitTelemetry.ts @@ -25,17 +25,17 @@ export class SubmitTelemetryRequest extends WorkerMessage { bw.writeU64(this.points.length) for (const point of this.points) { - bw.writeVarString(point.measurement) - bw.writeVarString(point.timestamp.toISOString()) + bw.writeVarString(point.measurement, 'utf8') + bw.writeVarString(point.timestamp.toISOString(), 'utf8') const { fields } = point bw.writeU64(fields.length) for (const field of fields) { - bw.writeVarString(field.name) - bw.writeVarString(field.type) + bw.writeVarString(field.name, 'utf8') + bw.writeVarString(field.type, 'utf8') switch (field.type) { case 'string': - bw.writeVarString(field.value) + bw.writeVarString(field.value, 'utf8') break case 'boolean': bw.writeU8(Number(field.value)) @@ -53,8 +53,8 @@ export class SubmitTelemetryRequest extends WorkerMessage { if (tags) { bw.writeU64(tags.length) for (const tag of tags) { - bw.writeVarString(tag.name) - bw.writeVarString(tag.value) + bw.writeVarString(tag.name, 'utf8') + bw.writeVarString(tag.value, 'utf8') } } } @@ -67,17 +67,17 @@ export class SubmitTelemetryRequest extends WorkerMessage { const pointsLength = reader.readU64() const points = [] for (let i = 0; i < pointsLength; i++) { - const measurement = reader.readVarString() - const timestamp = new Date(reader.readVarString()) + const measurement = reader.readVarString('utf8') + const timestamp = new Date(reader.readVarString('utf8')) const fieldsLength = reader.readU64() const fields = [] for (let j = 0; j < fieldsLength; j++) { - const name = reader.readVarString() - const type = reader.readVarString() + const name = reader.readVarString('utf8') + const type = reader.readVarString('utf8') switch (type) { case 'string': { - const value = reader.readVarString() + const value = reader.readVarString('utf8') fields.push({ name, type, value }) break } @@ -106,8 +106,8 @@ export class SubmitTelemetryRequest extends WorkerMessage { const tagsLength = reader.readU64() tags = [] for (let k = 0; k < tagsLength; k++) { - const name = reader.readVarString() - const value = reader.readVarString() + const name = reader.readVarString('utf8') + const value = reader.readVarString('utf8') tags.push({ name, value }) } } diff --git a/package.json b/package.json index 65352a9cf8..2ea69011e3 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "eslint-plugin-simple-import-sort": "7.0.0", "jest": "29.3.1", "jest-jasmine2": "29.3.1", - "lerna": "5.5.2", + "lerna": "6.4.1", "node-gyp": "8.4.1", "prettier": "2.3.2", "ts-jest": "29.0.3", diff --git a/yarn.lock b/yarn.lock index a42859532a..49e17cb7b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2366,39 +2366,39 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@lerna/add@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-5.5.2.tgz#d5970f408f7f8fa2eaa139e7d3c6a67bdd5fedb2" - integrity sha512-YCBpwDtNICvjTEG7klXITXFC8pZd8NrmkC8yseaTGm51VPNneZVPJZHWhOlWM4spn50ELVP1p2nnSl6COt50aw== - dependencies: - "@lerna/bootstrap" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/filter-options" "5.5.2" - "@lerna/npm-conf" "5.5.2" - "@lerna/validation-error" "5.5.2" +"@lerna/add@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/add/-/add-6.4.1.tgz#fa20fe9ff875dc5758141262c8cde0d9a6481ec4" + integrity sha512-YSRnMcsdYnQtQQK0NSyrS9YGXvB3jzvx183o+JTH892MKzSlBqwpBHekCknSibyxga1HeZ0SNKQXgsHAwWkrRw== + dependencies: + "@lerna/bootstrap" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/npm-conf" "6.4.1" + "@lerna/validation-error" "6.4.1" dedent "^0.7.0" npm-package-arg "8.1.1" p-map "^4.0.0" pacote "^13.6.1" semver "^7.3.4" -"@lerna/bootstrap@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-5.5.2.tgz#d5bedcc001cd4af35043ca5c77342276c8095853" - integrity sha512-oJ9G1MC/TMukJAZAf+bPJ2veAiiUj6/BGe99nagQh7uiXhH1N0uItd/aMC6xBHggu0ZVOQEY7mvL0/z1lGsM4w== - dependencies: - "@lerna/command" "5.5.2" - "@lerna/filter-options" "5.5.2" - "@lerna/has-npm-version" "5.5.2" - "@lerna/npm-install" "5.5.2" - "@lerna/package-graph" "5.5.2" - "@lerna/pulse-till-done" "5.5.2" - "@lerna/rimraf-dir" "5.5.2" - "@lerna/run-lifecycle" "5.5.2" - "@lerna/run-topologically" "5.5.2" - "@lerna/symlink-binary" "5.5.2" - "@lerna/symlink-dependencies" "5.5.2" - "@lerna/validation-error" "5.5.2" +"@lerna/bootstrap@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-6.4.1.tgz#a76ff22c3160d134fb60bcfddb3f8b0759b4f1ff" + integrity sha512-64cm0mnxzxhUUjH3T19ZSjPdn28vczRhhTXhNAvOhhU0sQgHrroam1xQC1395qbkV3iosSertlu8e7xbXW033w== + dependencies: + "@lerna/command" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/has-npm-version" "6.4.1" + "@lerna/npm-install" "6.4.1" + "@lerna/package-graph" "6.4.1" + "@lerna/pulse-till-done" "6.4.1" + "@lerna/rimraf-dir" "6.4.1" + "@lerna/run-lifecycle" "6.4.1" + "@lerna/run-topologically" "6.4.1" + "@lerna/symlink-binary" "6.4.1" + "@lerna/symlink-dependencies" "6.4.1" + "@lerna/validation-error" "6.4.1" "@npmcli/arborist" "5.3.0" dedent "^0.7.0" get-port "^5.1.1" @@ -2410,100 +2410,100 @@ p-waterfall "^2.1.1" semver "^7.3.4" -"@lerna/changed@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-5.5.2.tgz#903600271c58650bc1873e2441aaf9028658e1b8" - integrity sha512-/kF5TKkiXb0921aorZAMsNFAtcaVcDAvO7GndvcZZiDssc4K7weXhR+wsHi9e4dCJ2nVakhVJw0PqRNknd7x/A== +"@lerna/changed@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-6.4.1.tgz#4da6d08df7c53bc90c0c0d9d04839f91dd6d70a9" + integrity sha512-Z/z0sTm3l/iZW0eTSsnQpcY5d6eOpNO0g4wMOK+hIboWG0QOTc8b28XCnfCUO+33UisKl8PffultgoaHMKkGgw== dependencies: - "@lerna/collect-updates" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/listable" "5.5.2" - "@lerna/output" "5.5.2" + "@lerna/collect-updates" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/listable" "6.4.1" + "@lerna/output" "6.4.1" -"@lerna/check-working-tree@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-5.5.2.tgz#4f3de3efe2e8d0c6a62da4c66c17acf6776edaa6" - integrity sha512-FRkEe9Wcr8Lw3dR0AIOrWfODfEAcDKBF5Ol7bIA5wkPLMJbuPBgx4T1rABdRp94SVOnqkRwT9rrsFOESLcQJzQ== +"@lerna/check-working-tree@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-6.4.1.tgz#c0dcb5c474faf214865058e2fedda44962367a4e" + integrity sha512-EnlkA1wxaRLqhJdn9HX7h+JYxqiTK9aWEFOPqAE8lqjxHn3RpM9qBp1bAdL7CeUk3kN1lvxKwDEm0mfcIyMbPA== dependencies: - "@lerna/collect-uncommitted" "5.5.2" - "@lerna/describe-ref" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/collect-uncommitted" "6.4.1" + "@lerna/describe-ref" "6.4.1" + "@lerna/validation-error" "6.4.1" -"@lerna/child-process@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-5.5.2.tgz#f95d8aeb01c0cb6e6520bc9de28ff27c8516f588" - integrity sha512-JvTrIEDwq7bd0Nw/4TGAFa4miP8UKARfxhYwHkqX5vM+slNx3BiImkyDhG46C3zR2k/OrOK02CYbBUi6eI2OAw== +"@lerna/child-process@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-6.4.1.tgz#d697fb769f4c5b57c59f87471eb9b3d65be904a3" + integrity sha512-dvEKK0yKmxOv8pccf3I5D/k+OGiLxQp5KYjsrDtkes2pjpCFfQAMbmpol/Tqx6w/2o2rSaRrLsnX8TENo66FsA== dependencies: chalk "^4.1.0" execa "^5.0.0" strong-log-transformer "^2.1.0" -"@lerna/clean@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-5.5.2.tgz#5de2de1d66a66ee65dbea0b30513f784b4391bd2" - integrity sha512-C38x2B+yTg2zFWSV6/K6grX+7Dzgyw7YpRfhFr1Mat77mhku60lE3mqwU2qCLHlmKBmHV2rB85gYI8yysJ2rIg== +"@lerna/clean@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-6.4.1.tgz#e9ee365ee6879ee998b78b3269fad02b5f385771" + integrity sha512-FuVyW3mpos5ESCWSkQ1/ViXyEtsZ9k45U66cdM/HnteHQk/XskSQw0sz9R+whrZRUDu6YgYLSoj1j0YAHVK/3A== dependencies: - "@lerna/command" "5.5.2" - "@lerna/filter-options" "5.5.2" - "@lerna/prompt" "5.5.2" - "@lerna/pulse-till-done" "5.5.2" - "@lerna/rimraf-dir" "5.5.2" + "@lerna/command" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/prompt" "6.4.1" + "@lerna/pulse-till-done" "6.4.1" + "@lerna/rimraf-dir" "6.4.1" p-map "^4.0.0" p-map-series "^2.1.0" p-waterfall "^2.1.1" -"@lerna/cli@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-5.5.2.tgz#d4766d324908ebf9b5a9579ac5ee2f7deedcc9d4" - integrity sha512-u32ulEL5CBNYZOTG5dRrVJUT8DovDzjrLj/y/MKXpuD127PwWDe0TE//1NP8qagTLBtn5EiKqiuZlosAYTpiBA== +"@lerna/cli@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-6.4.1.tgz#2b2d093baace40e822caee8c90f698e98a437a2f" + integrity sha512-2pNa48i2wzFEd9LMPKWI3lkW/3widDqiB7oZUM1Xvm4eAOuDWc9I3RWmAUIVlPQNf3n4McxJCvsZZ9BpQN50Fg== dependencies: - "@lerna/global-options" "5.5.2" + "@lerna/global-options" "6.4.1" dedent "^0.7.0" npmlog "^6.0.2" yargs "^16.2.0" -"@lerna/collect-uncommitted@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-5.5.2.tgz#4397813f4b7ab169e427026548921c93f8be685c" - integrity sha512-2SzH21lDz016Dhu3MjmID9iCMTHYiZ/iu0UKT4I6glmDa44kre18Bp8ihyNzBXNWryj6KjB/0wxgb6dOtccw9A== +"@lerna/collect-uncommitted@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-6.4.1.tgz#ae62bcaa5ecaa5b7fbc41eb9ae90b6711be156ec" + integrity sha512-5IVQGhlLrt7Ujc5ooYA1Xlicdba/wMcDSnbQwr8ufeqnzV2z4729pLCVk55gmi6ZienH/YeBPHxhB5u34ofE0Q== dependencies: - "@lerna/child-process" "5.5.2" + "@lerna/child-process" "6.4.1" chalk "^4.1.0" npmlog "^6.0.2" -"@lerna/collect-updates@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-5.5.2.tgz#1278aa341b84fcc84ab4efb153464dcbc7706ee0" - integrity sha512-EeAazUjRenojQujM8W2zAxbw8/qEf5qd0pQYFKLCKkT8f332hoYzH8aJqnpAVY5vjFxxxxpjFjExfvMKqkwWVQ== +"@lerna/collect-updates@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-6.4.1.tgz#4f7cf1c411f3253d0104e7b64cb0aa315a5dfc81" + integrity sha512-pzw2/FC+nIqYkknUHK9SMmvP3MsLEjxI597p3WV86cEDN3eb1dyGIGuHiKShtjvT08SKSwpTX+3bCYvLVxtC5Q== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/describe-ref" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/describe-ref" "6.4.1" minimatch "^3.0.4" npmlog "^6.0.2" slash "^3.0.0" -"@lerna/command@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-5.5.2.tgz#4dcc4c772e82b9069b1b52a4947354825d0debaf" - integrity sha512-hcqKcngUCX6p9i2ipyzFVnTDZILAoxS0xn5YtLXLU2F16o/RIeEuhBrWeExhRXGBo1Rt3goxyq6/bKKaPU5i2Q== +"@lerna/command@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/command/-/command-6.4.1.tgz#96c4f5d88792c6c638738c66fcc3a7ad0d2487e2" + integrity sha512-3Lifj8UTNYbRad8JMP7IFEEdlIyclWyyvq/zvNnTS9kCOEymfmsB3lGXr07/AFoi6qDrvN64j7YSbPZ6C6qonw== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/package-graph" "5.5.2" - "@lerna/project" "5.5.2" - "@lerna/validation-error" "5.5.2" - "@lerna/write-log-file" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/package-graph" "6.4.1" + "@lerna/project" "6.4.1" + "@lerna/validation-error" "6.4.1" + "@lerna/write-log-file" "6.4.1" clone-deep "^4.0.1" dedent "^0.7.0" execa "^5.0.0" is-ci "^2.0.0" npmlog "^6.0.2" -"@lerna/conventional-commits@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-5.5.2.tgz#810da2733f4350e01f8320991296f6e1ba8d25c1" - integrity sha512-lFq1RTx41QEPU7N1yyqQRhVH1zPpRqWbdSpepBnSgeUKw/aE0pbkgNi+C6BKuSB/9OzY78j1OPbZSYrk4OWEBQ== +"@lerna/conventional-commits@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-6.4.1.tgz#b8d44a8a71865b4d37b900137acef623f3a0a11b" + integrity sha512-NIvCOjStjQy5O8VojB7/fVReNNDEJOmzRG2sTpgZ/vNS4AzojBQZ/tobzhm7rVkZZ43R9srZeuhfH9WgFsVUSA== dependencies: - "@lerna/validation-error" "5.5.2" + "@lerna/validation-error" "6.4.1" conventional-changelog-angular "^5.0.12" conventional-changelog-core "^4.2.4" conventional-recommended-bump "^6.1.0" @@ -2514,27 +2514,26 @@ pify "^5.0.0" semver "^7.3.4" -"@lerna/create-symlink@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-5.5.2.tgz#9593da204b2409bcd3555ad6f67b9d7cb5b7cdfc" - integrity sha512-/C0SP2C5+Lvol4Uul0/p0YJML/AOv1dO4y3NrRpYGnN750AuQMuhJQsBcHip80sFStKnNaUxXQb82itkL/mduw== +"@lerna/create-symlink@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-6.4.1.tgz#0efec22d78dd814a70d8345ced52c39beb05874b" + integrity sha512-rNivHFYV1GAULxnaTqeGb2AdEN2OZzAiZcx5CFgj45DWXQEGwPEfpFmCSJdXhFZbyd3K0uiDlAXjAmV56ov3FQ== dependencies: cmd-shim "^5.0.0" fs-extra "^9.1.0" npmlog "^6.0.2" -"@lerna/create@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-5.5.2.tgz#6091f550df9a389c9a8239e18f91b1acfdab1dcd" - integrity sha512-NawigXIAwPJjwDKTKo4aqmos8GIAYK8AQumwy027X418GzXf504L1acRm3c+3LmL1IrZTStWkqSNs56GrKRY9A== +"@lerna/create@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-6.4.1.tgz#3fc8556adadff1265432a6cee69ee14465798e71" + integrity sha512-qfQS8PjeGDDlxEvKsI/tYixIFzV2938qLvJohEKWFn64uvdLnXCamQ0wvRJST8p1ZpHWX4AXrB+xEJM3EFABrA== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/npm-conf" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/npm-conf" "6.4.1" + "@lerna/validation-error" "6.4.1" dedent "^0.7.0" fs-extra "^9.1.0" - globby "^11.0.2" init-package-json "^3.0.2" npm-package-arg "8.1.1" p-reduce "^2.1.0" @@ -2546,218 +2545,218 @@ validate-npm-package-name "^4.0.0" yargs-parser "20.2.4" -"@lerna/describe-ref@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-5.5.2.tgz#ba64e568bfbea8cca81b0a919550c33cf8359869" - integrity sha512-JY1Lk8sHX4mBk83t1wW8ak+QWzlExZluOMUixIWLhzHlOzRXnx/WJnvW3E2UgN/RFOBHsI8XA6RmzV/xd/D44Q== +"@lerna/describe-ref@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-6.4.1.tgz#c0a0beca5dfeada3a39b030f69c8c98f5623bb13" + integrity sha512-MXGXU8r27wl355kb1lQtAiu6gkxJ5tAisVJvFxFM1M+X8Sq56icNoaROqYrvW6y97A9+3S8Q48pD3SzkFv31Xw== dependencies: - "@lerna/child-process" "5.5.2" + "@lerna/child-process" "6.4.1" npmlog "^6.0.2" -"@lerna/diff@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-5.5.2.tgz#ff51712f1554cfea499954c406a79cea15744795" - integrity sha512-cBXCF/WXh59j6ydTObUB5vhij1cO1kmEVaW4su8rMqLy0eyAmYAckwnL4WIu3NUDlIm7ykaDp+itdAXPeUdDmw== +"@lerna/diff@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-6.4.1.tgz#ca9e62a451ce199faaa7ef5990ded3fad947e2f9" + integrity sha512-TnzJsRPN2fOjUrmo5Boi43fJmRtBJDsVgwZM51VnLoKcDtO1kcScXJ16Od2Xx5bXbp5dES5vGDLL/USVVWfeAg== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/validation-error" "6.4.1" npmlog "^6.0.2" -"@lerna/exec@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-5.5.2.tgz#70f64ec8c801905f9af30f1a6b955aa1160e142e" - integrity sha512-hwEIxSp3Gor5pMZp7jMrQ7qcfzyJOI5Zegj9K72M5KKRYSXI1uFxexZzN2ZJCso/rHg9H4TCa9P2wjmoo8KPag== - dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/filter-options" "5.5.2" - "@lerna/profiler" "5.5.2" - "@lerna/run-topologically" "5.5.2" - "@lerna/validation-error" "5.5.2" +"@lerna/exec@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-6.4.1.tgz#493ce805b6959e8299ec58fab8d31fd01ed209ba" + integrity sha512-KAWfuZpoyd3FMejHUORd0GORMr45/d9OGAwHitfQPVs4brsxgQFjbbBEEGIdwsg08XhkDb4nl6IYVASVTq9+gA== + dependencies: + "@lerna/child-process" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/profiler" "6.4.1" + "@lerna/run-topologically" "6.4.1" + "@lerna/validation-error" "6.4.1" p-map "^4.0.0" -"@lerna/filter-options@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-5.5.2.tgz#bf495abd596a170d8625281fadff052112fb2571" - integrity sha512-h9KrfntDjR1PTC0Xeu07dYytSdZ4jcKz/ykaqhELgXVDbzOUY9RnQd32e4XJ8KRSERMe4VS7DxOnxV4LNI0xqA== +"@lerna/filter-options@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-6.4.1.tgz#571d37436878fab8b2ac84ca1c3863acd3515cfb" + integrity sha512-efJh3lP2T+9oyNIP2QNd9EErf0Sm3l3Tz8CILMsNJpjSU6kO43TYWQ+L/ezu2zM99KVYz8GROLqDcHRwdr8qUA== dependencies: - "@lerna/collect-updates" "5.5.2" - "@lerna/filter-packages" "5.5.2" + "@lerna/collect-updates" "6.4.1" + "@lerna/filter-packages" "6.4.1" dedent "^0.7.0" npmlog "^6.0.2" -"@lerna/filter-packages@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-5.5.2.tgz#043784114fb0a8924b08536b5f62f0e741fc9362" - integrity sha512-EaZA0ibWKnpBePFt5gVbiTYgXwOs01naVPcPnBQt5EhHVN878rUoNXNnhT/X/KXFiiy6v3CW53sczlqTNoFuSg== +"@lerna/filter-packages@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-6.4.1.tgz#e138b182816a049c81de094069cad12aaa41a236" + integrity sha512-LCMGDGy4b+Mrb6xkcVzp4novbf5MoZEE6ZQF1gqG0wBWqJzNcKeFiOmf352rcDnfjPGZP6ct5+xXWosX/q6qwg== dependencies: - "@lerna/validation-error" "5.5.2" + "@lerna/validation-error" "6.4.1" multimatch "^5.0.0" npmlog "^6.0.2" -"@lerna/get-npm-exec-opts@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-5.5.2.tgz#a17489e5c4c5c180bee3095d1418782bdf7db615" - integrity sha512-CSwUpQrEYe20KEJnpdLxeLdYMaIElTQM9SiiFKUwnm/825TObkdDQ/fAG9Vk3fkHljPcu7SiV1A/g2XkbmpJUA== +"@lerna/get-npm-exec-opts@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-6.4.1.tgz#42681f6db4238277889b3423f87308eda5dc01ec" + integrity sha512-IvN/jyoklrWcjssOf121tZhOc16MaFPOu5ii8a+Oy0jfTriIGv929Ya8MWodj75qec9s+JHoShB8yEcMqZce4g== dependencies: npmlog "^6.0.2" -"@lerna/get-packed@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-5.5.2.tgz#f355773cbd295bc305ffc59050be9e6cdcc53825" - integrity sha512-C+2/oKqTdgskuK3SpoxzxJSffwQGRU/W8BA5rC/HmRN2xom8xlgZjP0Pcsv7ucW1BjE367hh+4E/BRZXPxuvVQ== +"@lerna/get-packed@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-6.4.1.tgz#b3b8b907002d50bf8792dd97e2729249c0b0e0cd" + integrity sha512-uaDtYwK1OEUVIXn84m45uPlXShtiUcw6V9TgB3rvHa3rrRVbR7D4r+JXcwVxLGrAS7LwxVbYWEEO/Z/bX7J/Lg== dependencies: fs-extra "^9.1.0" ssri "^9.0.1" tar "^6.1.0" -"@lerna/github-client@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-5.5.2.tgz#003ad2712786338b347d9675294be7e40f7f2a84" - integrity sha512-aIed5+l+QoiQmlCvcRoGgJ9z0Wo/7BZU0cbcds7OyhB6e723xtBTk3nXOASFI9TdcRcrnVpOFOISUKU+48d7Ig== +"@lerna/github-client@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-6.4.1.tgz#25d19b440395a6039b9162ee58dadb9dce990ff0" + integrity sha512-ridDMuzmjMNlcDmrGrV9mxqwUKzt9iYqCPwVYJlRYrnE3jxyg+RdooquqskVFj11djcY6xCV2Q2V1lUYwF+PmA== dependencies: - "@lerna/child-process" "5.5.2" + "@lerna/child-process" "6.4.1" "@octokit/plugin-enterprise-rest" "^6.0.1" "@octokit/rest" "^19.0.3" git-url-parse "^13.1.0" npmlog "^6.0.2" -"@lerna/gitlab-client@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-5.5.2.tgz#1d14b5f71e3e8074ea1894941702f32f0cff5031" - integrity sha512-iSNk8ktwRXL5JgTYvKdEQASHLgo8Vq4RLX1hOFhOMszxKeT2kjCXLqefto3TlJ5xOGQb/kaGBm++jp+uZxhdog== +"@lerna/gitlab-client@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-6.4.1.tgz#a01d962dc52a55b8272ea52bc54d72c5fd9db6f9" + integrity sha512-AdLG4d+jbUvv0jQyygQUTNaTCNSMDxioJso6aAjQ/vkwyy3fBJ6FYzX74J4adSfOxC2MQZITFyuG+c9ggp7pyQ== dependencies: node-fetch "^2.6.1" npmlog "^6.0.2" -"@lerna/global-options@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-5.5.2.tgz#4eafa90fb62036701ed04319adb33ab4901f1f5d" - integrity sha512-YaFCLMm7oThPpmRvrDX/VuoihrWCqBVm3zG+c8OM7sjs1MXDKycbdhtjzIwysWocEpf0NjUtdQS7v6gUhfNiFQ== +"@lerna/global-options@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-6.4.1.tgz#7df76b1d38500606a8dc3ce0804bab6894c4f4a3" + integrity sha512-UTXkt+bleBB8xPzxBPjaCN/v63yQdfssVjhgdbkQ//4kayaRA65LyEtJTi9rUrsLlIy9/rbeb+SAZUHg129fJg== -"@lerna/has-npm-version@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-5.5.2.tgz#4bb84f223aa6a6b608e057b6a3dc7bd96dbbe03f" - integrity sha512-8BHJCVPy5o0vERm0jjcwYSCNOK+EclbufR05kqorsYzCu0xWPOc3SDlo5mXuWsG61SlT3RdV9SJ3Rab15fOLAg== +"@lerna/has-npm-version@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-6.4.1.tgz#04eba7df687e665294834253b659430efc1e01bb" + integrity sha512-vW191w5iCkwNWWWcy4542ZOpjKYjcP/pU3o3+w6NM1J3yBjWZcNa8lfzQQgde2QkGyNi+i70o6wIca1o0sdKwg== dependencies: - "@lerna/child-process" "5.5.2" + "@lerna/child-process" "6.4.1" semver "^7.3.4" -"@lerna/import@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-5.5.2.tgz#7f18d17723a320ceea694955c351c7c8d60e3152" - integrity sha512-QtHJEo/9RRO9oILzSK45k5apsAyUEgwpGj4Ys3gZ7rFuXQ4+xHi9R6YC0IjwyiSfoN/i3Qbsku+PByxhhzkxHQ== +"@lerna/import@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/import/-/import-6.4.1.tgz#b5696fed68a32d32398d66f95192267f1da5110e" + integrity sha512-oDg8g1PNrCM1JESLsG3rQBtPC+/K9e4ohs0xDKt5E6p4l7dc0Ib4oo0oCCT/hGzZUlNwHxrc2q9JMRzSAn6P/Q== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/prompt" "5.5.2" - "@lerna/pulse-till-done" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/prompt" "6.4.1" + "@lerna/pulse-till-done" "6.4.1" + "@lerna/validation-error" "6.4.1" dedent "^0.7.0" fs-extra "^9.1.0" p-map-series "^2.1.0" -"@lerna/info@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/info/-/info-5.5.2.tgz#8ae7b2efb64579f6aa153c597cd2da99e2716802" - integrity sha512-Ek+bCooAfng+K4Fgy9i6jKBMpZZQ3lQpv6SWg8TbrwGR/el8FYBJod3+I5khJ2RJqHAmjLBz6wiSyVPMjwvptw== +"@lerna/info@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/info/-/info-6.4.1.tgz#30354fcb82c99b1f0ed753f957fbaca5b250c3fa" + integrity sha512-Ks4R7IndIr4vQXz+702gumPVhH6JVkshje0WKA3+ew2qzYZf68lU1sBe1OZsQJU3eeY2c60ax+bItSa7aaIHGw== dependencies: - "@lerna/command" "5.5.2" - "@lerna/output" "5.5.2" + "@lerna/command" "6.4.1" + "@lerna/output" "6.4.1" envinfo "^7.7.4" -"@lerna/init@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-5.5.2.tgz#51439bacfcf6670bda042541566432836c6fb54e" - integrity sha512-CKHrcOlm2XXXF384FeCKK+CjKBW22HkJ5CcLlU1gnTFD2QarrBwTOGjpRaREXP8T/k3q7h0W0FK8B77opqLwDg== +"@lerna/init@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/init/-/init-6.4.1.tgz#ea4905ca976189db4b0bf04d78919060146bf684" + integrity sha512-CXd/s/xgj0ZTAoOVyolOTLW2BG7uQOhWW4P/ktlwwJr9s3c4H/z+Gj36UXw3q5X1xdR29NZt7Vc6fvROBZMjUQ== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/project" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/project" "6.4.1" fs-extra "^9.1.0" p-map "^4.0.0" write-json-file "^4.3.0" -"@lerna/link@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-5.5.2.tgz#ed9225f29cb8887f5da124ebb54f7e0c978896bb" - integrity sha512-B/0a+biXO2uMSbNw1Vv9YMrfse0i8HU9mrrWQbXIHws3j0i5Wxuxvd7B/r0xzYN5LF5AFDxrPjPNTgC49U/58Q== +"@lerna/link@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/link/-/link-6.4.1.tgz#f31ed1f6aea1581e358a9ff545be78b61e923175" + integrity sha512-O8Rt7MAZT/WT2AwrB/+HY76ktnXA9cDFO9rhyKWZGTHdplbzuJgfsGzu8Xv0Ind+w+a8xLfqtWGPlwiETnDyrw== dependencies: - "@lerna/command" "5.5.2" - "@lerna/package-graph" "5.5.2" - "@lerna/symlink-dependencies" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/command" "6.4.1" + "@lerna/package-graph" "6.4.1" + "@lerna/symlink-dependencies" "6.4.1" + "@lerna/validation-error" "6.4.1" p-map "^4.0.0" slash "^3.0.0" -"@lerna/list@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-5.5.2.tgz#dfebcaae284bb25d2a5d1223086a95cd22bc4701" - integrity sha512-uC/LRq9zcOM33vV6l4Nmx18vXNNIcaxFHVCBOC3IxZJb0MTPzKFqlu/YIVQaJMWeHpiIo6OfbK4mbH1h8yXmHw== +"@lerna/list@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/list/-/list-6.4.1.tgz#12ad83902e148d1e5ba007149b72b14636f9f1ba" + integrity sha512-7a6AKgXgC4X7nK6twVPNrKCiDhrCiAhL/FE4u9HYhHqw9yFwyq8Qe/r1RVOkAOASNZzZ8GuBvob042bpunupCw== dependencies: - "@lerna/command" "5.5.2" - "@lerna/filter-options" "5.5.2" - "@lerna/listable" "5.5.2" - "@lerna/output" "5.5.2" + "@lerna/command" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/listable" "6.4.1" + "@lerna/output" "6.4.1" -"@lerna/listable@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-5.5.2.tgz#ed2858acef7886067ff373f501102999caf86c55" - integrity sha512-CEDTaLB8V7faSSTgB1II1USpda5PQWUkfsvDJekJ4yZ4dql3XnzqdVZ48zLqPArl/30e0g1gWGOBkdKqswY+Yg== +"@lerna/listable@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-6.4.1.tgz#6f5c83865391c6beeb41802951c674e2de119bde" + integrity sha512-L8ANeidM10aoF8aL3L/771Bb9r/TRkbEPzAiC8Iy2IBTYftS87E3rT/4k5KBEGYzMieSKJaskSFBV0OQGYV1Cw== dependencies: - "@lerna/query-graph" "5.5.2" + "@lerna/query-graph" "6.4.1" chalk "^4.1.0" columnify "^1.6.0" -"@lerna/log-packed@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-5.5.2.tgz#3f651f2d010e830aa3dfe34b59ba8767c29e0658" - integrity sha512-k1tKZdNuAIj9t7ZJBSzua5zEnPoweKLpuXYzuiBE8CALBfl2Zf9szsbDQDsERDOxQ365+FEgK+GfkmvxtYx4tw== +"@lerna/log-packed@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-6.4.1.tgz#43eae50d5c0cd906b1977a58b62b35541cf89ec1" + integrity sha512-Pwv7LnIgWqZH4vkM1rWTVF+pmWJu7d0ZhVwyhCaBJUsYbo+SyB2ZETGygo3Z/A+vZ/S7ImhEEKfIxU9bg5lScQ== dependencies: byte-size "^7.0.0" columnify "^1.6.0" has-unicode "^2.0.1" npmlog "^6.0.2" -"@lerna/npm-conf@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-5.5.2.tgz#bec9b5d7d729be86386a154170bd65e6057578c6" - integrity sha512-X2EE1TCSfsYy2XTUUN0+QXXEPvecuGk3mpTXR5KP+ScAs0WmTisRLyJ9lofh/9e0SIIGdVYmh2PykhgduyOKsg== +"@lerna/npm-conf@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-6.4.1.tgz#64dba237ff41472a24f96192669c1bc0dce15edb" + integrity sha512-Q+83uySGXYk3n1pYhvxtzyGwBGijYgYecgpiwRG1YNyaeGy+Mkrj19cyTWubT+rU/kM5c6If28+y9kdudvc7zQ== dependencies: config-chain "^1.1.12" pify "^5.0.0" -"@lerna/npm-dist-tag@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-5.5.2.tgz#56b441efb85cd3de88f15c97553942372da9a774" - integrity sha512-Od4liA0ISunwatHxArHdaxFc/m9dXMI0fAFqbScgeqVkY8OeoHEY/AlINjglYChtGcbKdHm1ml8qvlK9Tr2EXg== +"@lerna/npm-dist-tag@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-6.4.1.tgz#f14e7176f7e323284e8aa8636b44818a61738fd1" + integrity sha512-If1Hn4q9fn0JWuBm455iIZDWE6Fsn4Nv8Tpqb+dYf0CtoT5Hn+iT64xSiU5XJw9Vc23IR7dIujkEXm2MVbnvZw== dependencies: - "@lerna/otplease" "5.5.2" + "@lerna/otplease" "6.4.1" npm-package-arg "8.1.1" npm-registry-fetch "^13.3.0" npmlog "^6.0.2" -"@lerna/npm-install@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-5.5.2.tgz#26dcbf45b27f06e9744b9283c23d30d7feeaabb5" - integrity sha512-aDIDRS9C9uWheuc6JEntNqTcaTcSFyTx4FgUw5FDHrwsTZ9TiEAB9O+XyDKIlcGHlNviuQt270boUHjsvOoMcg== +"@lerna/npm-install@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-6.4.1.tgz#99f5748cb43de9786ea2b538c94a7183d38fc476" + integrity sha512-7gI1txMA9qTaT3iiuk/8/vL78wIhtbbOLhMf8m5yQ2G+3t47RUA8MNgUMsq4Zszw9C83drayqesyTf0u8BzVRg== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/get-npm-exec-opts" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/get-npm-exec-opts" "6.4.1" fs-extra "^9.1.0" npm-package-arg "8.1.1" npmlog "^6.0.2" signal-exit "^3.0.3" write-pkg "^4.0.0" -"@lerna/npm-publish@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-5.5.2.tgz#be9c2f8eaf1ab21619ad43434e6f69a56e9eda28" - integrity sha512-TRYkkocg/VFy9MwWtfIa2gNXFkMwkDfaS1exgJK4DKbjH3hiBo/cDG3Zx/jMBGvetv4CLsC2n+phRhozgCezTA== +"@lerna/npm-publish@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-6.4.1.tgz#baf07b108ae8b32932612db63206bcd5b5ee0e88" + integrity sha512-lbNEg+pThPAD8lIgNArm63agtIuCBCF3umxvgTQeLzyqUX6EtGaKJFyz/6c2ANcAuf8UfU7WQxFFbOiolibXTQ== dependencies: - "@lerna/otplease" "5.5.2" - "@lerna/run-lifecycle" "5.5.2" + "@lerna/otplease" "6.4.1" + "@lerna/run-lifecycle" "6.4.1" fs-extra "^9.1.0" libnpmpublish "^6.0.4" npm-package-arg "8.1.1" @@ -2765,85 +2764,85 @@ pify "^5.0.0" read-package-json "^5.0.1" -"@lerna/npm-run-script@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-5.5.2.tgz#790ac839f3f761deb017dd02a666488be0a7f24b" - integrity sha512-lKn4ybw/97SMR/0j5UcJraL+gpfXv2HWKmlrG47JuAMJaEFkQQyCh4EdP3cGPCnSzrI5zXsil8SS/JelkhQpkg== +"@lerna/npm-run-script@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-6.4.1.tgz#86db4f15d359b8a371db666aa51c9b2b87b602f3" + integrity sha512-HyvwuyhrGqDa1UbI+pPbI6v+wT6I34R0PW3WCADn6l59+AyqLOCUQQr+dMW7jdYNwjO6c/Ttbvj4W58EWsaGtQ== dependencies: - "@lerna/child-process" "5.5.2" - "@lerna/get-npm-exec-opts" "5.5.2" + "@lerna/child-process" "6.4.1" + "@lerna/get-npm-exec-opts" "6.4.1" npmlog "^6.0.2" -"@lerna/otplease@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-5.5.2.tgz#ae49aa9e2298d68282f641ebd7a94d1b9677e28b" - integrity sha512-kZwSWTLGFWLoFX0p6RJ8AARIo6P/wkIcUyAFrVU3YTesN7KqbujpzaVTf5bAWsDdeiRWizCGM1TVw2IDUtStQg== +"@lerna/otplease@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-6.4.1.tgz#9573e053c43e7139442da96fe655aa02749cb8a3" + integrity sha512-ePUciFfFdythHNMp8FP5K15R/CoGzSLVniJdD50qm76c4ATXZHnGCW2PGwoeAZCy4QTzhlhdBq78uN0wAs75GA== dependencies: - "@lerna/prompt" "5.5.2" + "@lerna/prompt" "6.4.1" -"@lerna/output@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/output/-/output-5.5.2.tgz#2c0aa22c15f887ff1835d15fdf7ca198110f2fb7" - integrity sha512-Sv5qMvwnY7RGUw3JHyNUHNlQ4f/167kK1tczCaHUXa1SmOq5adMBbiMNApa2y5s8B+v9OahkU2nnOOaIuVy0HQ== +"@lerna/output@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/output/-/output-6.4.1.tgz#327baf768b8fb63db9d52f68288d387379f814f7" + integrity sha512-A1yRLF0bO+lhbIkrryRd6hGSD0wnyS1rTPOWJhScO/Zyv8vIPWhd2fZCLR1gI2d/Kt05qmK3T/zETTwloK7Fww== dependencies: npmlog "^6.0.2" -"@lerna/pack-directory@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-5.5.2.tgz#4a499fd2d2deeed0d2a1859c51b98f96b4b7ef9f" - integrity sha512-LvBbOeSwbpHPL7w9cI0Jtpa6r61N3KboD4nutNlWaT9LRv0dLlex2k10Pfc8u15agQ62leLhHa6UmjFt16msEA== +"@lerna/pack-directory@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-6.4.1.tgz#e78aae4e7944057d8fc6cb4dd8ae50be7a95c2fd" + integrity sha512-kBtDL9bPP72/Nl7Gqa2CA3Odb8CYY1EF2jt801f+B37TqRLf57UXQom7yF3PbWPCPmhoU+8Fc4RMpUwSbFC46Q== dependencies: - "@lerna/get-packed" "5.5.2" - "@lerna/package" "5.5.2" - "@lerna/run-lifecycle" "5.5.2" - "@lerna/temp-write" "5.5.2" + "@lerna/get-packed" "6.4.1" + "@lerna/package" "6.4.1" + "@lerna/run-lifecycle" "6.4.1" + "@lerna/temp-write" "6.4.1" npm-packlist "^5.1.1" npmlog "^6.0.2" tar "^6.1.0" -"@lerna/package-graph@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-5.5.2.tgz#ae1d52f520f376cf819823fe16524c0f39c6b32c" - integrity sha512-tyMokkrktvohhU3PE3nZLdjrmozcrV8ql37u0l/axHXrfNiV3RDn9ENVvYXnLnP2BCHV572RRpbI5kYto4wtRg== +"@lerna/package-graph@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-6.4.1.tgz#7a18024d531f0bd88609944e572b4861f0f8868f" + integrity sha512-fQvc59stRYOqxT3Mn7g/yI9/Kw5XetJoKcW5l8XeqKqcTNDURqKnN0qaNBY6lTTLOe4cR7gfXF2l1u3HOz0qEg== dependencies: - "@lerna/prerelease-id-from-version" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/prerelease-id-from-version" "6.4.1" + "@lerna/validation-error" "6.4.1" npm-package-arg "8.1.1" npmlog "^6.0.2" semver "^7.3.4" -"@lerna/package@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/package/-/package-5.5.2.tgz#5f692289d1164a4d3456cba0c45ec6ea5fc0f4a7" - integrity sha512-/36+oq5Q63EYSyjW5mHPR3aMrXDo6Wn8zKcl9Dfd4bn+w0AfK/EbId7iB/TrFaNdGtw8CrhK+e5CmgiMBeXMPw== +"@lerna/package@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/package/-/package-6.4.1.tgz#ebbd4c5f58f4b6cf77019271a686be9585272a3b" + integrity sha512-TrOah58RnwS9R8d3+WgFFTu5lqgZs7M+e1dvcRga7oSJeKscqpEK57G0xspvF3ycjfXQwRMmEtwPmpkeEVLMzA== dependencies: load-json-file "^6.2.0" npm-package-arg "8.1.1" write-pkg "^4.0.0" -"@lerna/prerelease-id-from-version@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-5.5.2.tgz#0d27ce30aca010266db8f0de86509b44778cc1f3" - integrity sha512-FokuA8PFH+YMlbVvPsrTWgfZzaeXDmSmXGKzF8yEM7008UOFx9a3ivDzPnRK7IDaO9nUmt++Snb3QLey1ldYlQ== +"@lerna/prerelease-id-from-version@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-6.4.1.tgz#65eb1835cdfd112783eea6b596812c64f535386b" + integrity sha512-uGicdMFrmfHXeC0FTosnUKRgUjrBJdZwrmw7ZWMb5DAJGOuTzrvJIcz5f0/eL3XqypC/7g+9DoTgKjX3hlxPZA== dependencies: semver "^7.3.4" -"@lerna/profiler@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-5.5.2.tgz#b977a5b59388671b9bb6b4d3a2e7ae84a228d121" - integrity sha512-030TM1sG0h/vSJ+49e8K1HtVIt94i6lOIRILTF4zkx+O00Fcg91wBtdIduKhZZt1ziWRi1v2soijKR26IDC+Tg== +"@lerna/profiler@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/profiler/-/profiler-6.4.1.tgz#0d5e017e1389e35960d671f43db7eb16337fda1b" + integrity sha512-dq2uQxcu0aq6eSoN+JwnvHoAnjtZAVngMvywz5bTAfzz/sSvIad1v8RCpJUMBQHxaPtbfiNvOIQgDZOmCBIM4g== dependencies: fs-extra "^9.1.0" npmlog "^6.0.2" upath "^2.0.1" -"@lerna/project@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/project/-/project-5.5.2.tgz#02d1f031509347e62e665c862dd319703ce5528a" - integrity sha512-NtHov7CCM3DHbj6xaD9lTErOnEmz0s+piJP/nVw6aIvfkhvUl1fB6SnttM+0GHZrT6WSIXFWsb0pkRMTBn55Bw== +"@lerna/project@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/project/-/project-6.4.1.tgz#0519323aa8bde5b73fc0bf1c428385a556a445f0" + integrity sha512-BPFYr4A0mNZ2jZymlcwwh7PfIC+I6r52xgGtJ4KIrIOB6mVKo9u30dgYJbUQxmSuMRTOnX7PJZttQQzSda4gEg== dependencies: - "@lerna/package" "5.5.2" - "@lerna/validation-error" "5.5.2" + "@lerna/package" "6.4.1" + "@lerna/validation-error" "6.4.1" cosmiconfig "^7.0.0" dedent "^0.7.0" dot-prop "^6.0.1" @@ -2856,38 +2855,38 @@ resolve-from "^5.0.0" write-json-file "^4.3.0" -"@lerna/prompt@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-5.5.2.tgz#d21e1ef3d18ad5cf2418c640927bbb64f54e72dd" - integrity sha512-flV5SOu9CZrTf2YxGgMPwiAsv2jkUzyIs3cTTdFhFtKoZV7YPVZkGyMhqhEMIuUCOeITFY+emar9iPS6d7U4Jg== +"@lerna/prompt@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-6.4.1.tgz#5ede06b4c8e17ec3045180b10ec5bd313cbc8585" + integrity sha512-vMxCIgF9Vpe80PnargBGAdS/Ib58iYEcfkcXwo7mYBCxEVcaUJFKZ72FEW8rw+H5LkxBlzrBJyfKRoOe0ks9gQ== dependencies: inquirer "^8.2.4" npmlog "^6.0.2" -"@lerna/publish@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-5.5.2.tgz#4253ffa0bbd55b7b4380e247c6039a2f283b13a6" - integrity sha512-ZC8LP4I3nLcVIcyqiRAVvGRaCkHHBdYVcqtF7S9KA8w2VvuAeqHRFUTIhKBziVbYnwI2uzJXGIRWP50U+p/wAA== - dependencies: - "@lerna/check-working-tree" "5.5.2" - "@lerna/child-process" "5.5.2" - "@lerna/collect-updates" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/describe-ref" "5.5.2" - "@lerna/log-packed" "5.5.2" - "@lerna/npm-conf" "5.5.2" - "@lerna/npm-dist-tag" "5.5.2" - "@lerna/npm-publish" "5.5.2" - "@lerna/otplease" "5.5.2" - "@lerna/output" "5.5.2" - "@lerna/pack-directory" "5.5.2" - "@lerna/prerelease-id-from-version" "5.5.2" - "@lerna/prompt" "5.5.2" - "@lerna/pulse-till-done" "5.5.2" - "@lerna/run-lifecycle" "5.5.2" - "@lerna/run-topologically" "5.5.2" - "@lerna/validation-error" "5.5.2" - "@lerna/version" "5.5.2" +"@lerna/publish@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-6.4.1.tgz#e1bdfa67297ca4a3054863e7acfc8482bf613c35" + integrity sha512-/D/AECpw2VNMa1Nh4g29ddYKRIqygEV1ftV8PYXVlHpqWN7VaKrcbRU6pn0ldgpFlMyPtESfv1zS32F5CQ944w== + dependencies: + "@lerna/check-working-tree" "6.4.1" + "@lerna/child-process" "6.4.1" + "@lerna/collect-updates" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/describe-ref" "6.4.1" + "@lerna/log-packed" "6.4.1" + "@lerna/npm-conf" "6.4.1" + "@lerna/npm-dist-tag" "6.4.1" + "@lerna/npm-publish" "6.4.1" + "@lerna/otplease" "6.4.1" + "@lerna/output" "6.4.1" + "@lerna/pack-directory" "6.4.1" + "@lerna/prerelease-id-from-version" "6.4.1" + "@lerna/prompt" "6.4.1" + "@lerna/pulse-till-done" "6.4.1" + "@lerna/run-lifecycle" "6.4.1" + "@lerna/run-topologically" "6.4.1" + "@lerna/validation-error" "6.4.1" + "@lerna/version" "6.4.1" fs-extra "^9.1.0" libnpmaccess "^6.0.3" npm-package-arg "8.1.1" @@ -2898,99 +2897,100 @@ pacote "^13.6.1" semver "^7.3.4" -"@lerna/pulse-till-done@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-5.5.2.tgz#b71aa52971ecfc75b756151321f0bef49d9e3ff4" - integrity sha512-e8sRby4FxSU9QjdRYXvHQtb5GMVO5XDnSH83RWdSxAVFGVEVWKqI3qg3otGH1JlD/kOu195d+ZzndF9qqMvveQ== +"@lerna/pulse-till-done@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-6.4.1.tgz#85c38a43939bf5e21b61091d0bcf73a1109a59db" + integrity sha512-efAkOC1UuiyqYBfrmhDBL6ufYtnpSqAG+lT4d/yk3CzJEJKkoCwh2Hb692kqHHQ5F74Uusc8tcRB7GBcfNZRWA== dependencies: npmlog "^6.0.2" -"@lerna/query-graph@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-5.5.2.tgz#3bfe53430936a62c3f225cb5af91709876da982e" - integrity sha512-krKt+mvGm+9fp71ZGUO1MiUZsL+W6dAKx5kBPNWkrw5TFZCasZJHRSIqby9iXpjma+MYohjFjLVvg1PIYKt/kg== +"@lerna/query-graph@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-6.4.1.tgz#3c224a49ff392d08ce8aeeaa1af4458f522a2b78" + integrity sha512-gBGZLgu2x6L4d4ZYDn4+d5rxT9RNBC+biOxi0QrbaIq83I+JpHVmFSmExXK3rcTritrQ3JT9NCqb+Yu9tL9adQ== dependencies: - "@lerna/package-graph" "5.5.2" + "@lerna/package-graph" "6.4.1" -"@lerna/resolve-symlink@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-5.5.2.tgz#ba1e3a04600b6ffae604e522e5a4abf2bf52b936" - integrity sha512-JLJg6/IFqpmGjFfKvj+lntcsGGWbIxF2uAcrVKldqwcPTmlMvolg51lL+wqII3s8N3gZIGdxhjXfhDdKuKtEzQ== +"@lerna/resolve-symlink@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-6.4.1.tgz#ab42dcbd03bc4028ec77ee481c5db8884ebaf40a" + integrity sha512-gnqltcwhWVLUxCuwXWe/ch9WWTxXRI7F0ZvCtIgdfOpbosm3f1g27VO1LjXeJN2i6ks03qqMowqy4xB4uMR9IA== dependencies: fs-extra "^9.1.0" npmlog "^6.0.2" read-cmd-shim "^3.0.0" -"@lerna/rimraf-dir@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-5.5.2.tgz#e1de764dadd7ca305d1d2698676f5fcfbe0d0ada" - integrity sha512-siE1RpEpSLFlnnbAJZz+CuBIcOqXrhR/SXVBnPDpIg4tGgHns+Q99m6K29ltuh+vZMBLMYnnyfPYitJFYTC3MQ== +"@lerna/rimraf-dir@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-6.4.1.tgz#116e379f653135b3ae955dcba703bdf212cab51a" + integrity sha512-5sDOmZmVj0iXIiEgdhCm0Prjg5q2SQQKtMd7ImimPtWKkV0IyJWxrepJFbeQoFj5xBQF7QB5jlVNEfQfKhD6pQ== dependencies: - "@lerna/child-process" "5.5.2" + "@lerna/child-process" "6.4.1" npmlog "^6.0.2" path-exists "^4.0.0" rimraf "^3.0.2" -"@lerna/run-lifecycle@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-5.5.2.tgz#8a4faa272007495729b7ef39206b47cde094074a" - integrity sha512-d5pF0abAv6MVNG3xhG1BakHZtr93vIn27aqgBvu9XK1CW6GdbpBpCv1kc8RjHyOpjjFDt4+uK2TG7s7T0oCZPw== +"@lerna/run-lifecycle@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-6.4.1.tgz#1eac136afae97e197bdb564e67fb385f4d346685" + integrity sha512-42VopI8NC8uVCZ3YPwbTycGVBSgukJltW5Saein0m7TIqFjwSfrcP0n7QJOr+WAu9uQkk+2kBstF5WmvKiqgEA== dependencies: - "@lerna/npm-conf" "5.5.2" + "@lerna/npm-conf" "6.4.1" "@npmcli/run-script" "^4.1.7" npmlog "^6.0.2" p-queue "^6.6.2" -"@lerna/run-topologically@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-5.5.2.tgz#e485f7ce859198ad0e487814ea8ca83ebcb17ada" - integrity sha512-o3XYXk7hG8ijUjejgXoa7fuQvzEohMUm4AB5SPBbvq1BhoqIZfW50KlBNjud1zVD4OsA8jJOfjItcY9KfxowuA== +"@lerna/run-topologically@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-6.4.1.tgz#640b07d83f1d1e6d3bc36f81a74957839bb1672f" + integrity sha512-gXlnAsYrjs6KIUGDnHM8M8nt30Amxq3r0lSCNAt+vEu2sMMEOh9lffGGaJobJZ4bdwoXnKay3uER/TU8E9owMw== dependencies: - "@lerna/query-graph" "5.5.2" + "@lerna/query-graph" "6.4.1" p-queue "^6.6.2" -"@lerna/run@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-5.5.2.tgz#8449406d9257def944b9cba28c76ed12246bc8a4" - integrity sha512-KVMkjL2ehW+/6VAwTTLgq82Rgw4W6vOz1I9XwwO/bk9h7DoY1HlE8leaaYRNqT+Cv437A9AwggR+LswhoK3alA== - dependencies: - "@lerna/command" "5.5.2" - "@lerna/filter-options" "5.5.2" - "@lerna/npm-run-script" "5.5.2" - "@lerna/output" "5.5.2" - "@lerna/profiler" "5.5.2" - "@lerna/run-topologically" "5.5.2" - "@lerna/timer" "5.5.2" - "@lerna/validation-error" "5.5.2" +"@lerna/run@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/run/-/run-6.4.1.tgz#985279f071ff23ae15f92837f85f979a1352fc01" + integrity sha512-HRw7kS6KNqTxqntFiFXPEeBEct08NjnL6xKbbOV6pXXf+lXUQbJlF8S7t6UYqeWgTZ4iU9caIxtZIY+EpW93mQ== + dependencies: + "@lerna/command" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/npm-run-script" "6.4.1" + "@lerna/output" "6.4.1" + "@lerna/profiler" "6.4.1" + "@lerna/run-topologically" "6.4.1" + "@lerna/timer" "6.4.1" + "@lerna/validation-error" "6.4.1" fs-extra "^9.1.0" + nx ">=15.4.2 < 16" p-map "^4.0.0" -"@lerna/symlink-binary@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-5.5.2.tgz#0227875212576e2a20a450ebe3362bfa7708284a" - integrity sha512-fQAN0ClwlVLThqm+m9d4lIfa2TuONocdNQocmou8UBDI/C/VVW6dvD+tSL3I4jYIYJWsXJe1hBBjil4ZYXpQrQ== +"@lerna/symlink-binary@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-6.4.1.tgz#d8e1b653a7ae9fe38834851c66c92278e3bb25ae" + integrity sha512-poZX90VmXRjL/JTvxaUQPeMDxFUIQvhBkHnH+dwW0RjsHB/2Tu4QUAsE0OlFnlWQGsAtXF4FTtW8Xs57E/19Kw== dependencies: - "@lerna/create-symlink" "5.5.2" - "@lerna/package" "5.5.2" + "@lerna/create-symlink" "6.4.1" + "@lerna/package" "6.4.1" fs-extra "^9.1.0" p-map "^4.0.0" -"@lerna/symlink-dependencies@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-5.5.2.tgz#f97eab64a0ad0702ef2da1690e7eeafb1c4e5c29" - integrity sha512-eNIICnlUD1YCiIY50O2TKHkxXCF4rYAFOCVWTiUS098tNKLssTPnIQrK3ASKxK9t7srmfcm49LFxNRPjVKjSBw== +"@lerna/symlink-dependencies@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-6.4.1.tgz#988203cc260406b64d61294367821a0f26419ee6" + integrity sha512-43W2uLlpn3TTYuHVeO/2A6uiTZg6TOk/OSKi21ujD7IfVIYcRYCwCV+8LPP12R3rzyab0JWkWnhp80Z8A2Uykw== dependencies: - "@lerna/create-symlink" "5.5.2" - "@lerna/resolve-symlink" "5.5.2" - "@lerna/symlink-binary" "5.5.2" + "@lerna/create-symlink" "6.4.1" + "@lerna/resolve-symlink" "6.4.1" + "@lerna/symlink-binary" "6.4.1" fs-extra "^9.1.0" p-map "^4.0.0" p-map-series "^2.1.0" -"@lerna/temp-write@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/temp-write/-/temp-write-5.5.2.tgz#86cb3b3190bcb959d84bb2e06a910543f3957af3" - integrity sha512-K/9L+25qIw4qw/SSLxwfAWzaUE3luqGTusd3x934Hg2sBQVX28xddwaZlasQ6qen7ETp6Ec9vSVWF2ffWTxKJg== +"@lerna/temp-write@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/temp-write/-/temp-write-6.4.1.tgz#1c46d05b633597c77b0c5f5ab46c1315195f7786" + integrity sha512-7uiGFVoTyos5xXbVQg4bG18qVEn9dFmboXCcHbMj5mc/+/QmU9QeNz/Cq36O5TY6gBbLnyj3lfL5PhzERWKMFg== dependencies: graceful-fs "^4.1.15" is-stream "^2.0.0" @@ -2998,37 +2998,38 @@ temp-dir "^1.0.0" uuid "^8.3.2" -"@lerna/timer@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-5.5.2.tgz#d28b4c4431e2988e0c308d8c9d98c503416dae21" - integrity sha512-QcnMFwcP7xlT9DH4oGVuDYuSOfpAghG4wj7D8vN1GhJFd9ueDCzTFJpFRd6INacIbESBNMjq5WuTeNdxcDo8Fg== +"@lerna/timer@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-6.4.1.tgz#47fe50b56bd2fc32396a2559f7bb65de8200f07d" + integrity sha512-ogmjFTWwRvevZr76a2sAbhmu3Ut2x73nDIn0bcwZwZ3Qc3pHD8eITdjs/wIKkHse3J7l3TO5BFJPnrvDS7HLnw== -"@lerna/validation-error@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-5.5.2.tgz#6ef92fdfab30404fc7d3668499c03c5740158d81" - integrity sha512-ZffmtrgOkihUxpho529rDI0llDV9YFNJqh0qF2+doFePeTtFKkFVFHZvxP9hPZPMOLypX9OHwCVfMaTlIpIjjA== +"@lerna/validation-error@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-6.4.1.tgz#2cab92c2be395158c3d65fa57ddb73892617d7e8" + integrity sha512-fxfJvl3VgFd7eBfVMRX6Yal9omDLs2mcGKkNYeCEyt4Uwlz1B5tPAXyk/sNMfkKV2Aat/mlK5tnY13vUrMKkyA== dependencies: npmlog "^6.0.2" -"@lerna/version@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-5.5.2.tgz#938020878fe274d8569cbd443c4c14732afd8c67" - integrity sha512-MMO0rnC9Y8JQEl6+XJMu0JM/bWpe6mGNhQJ8C9W1hkpMwxrizhcoEFb9Vq/q/tw7DjCVc3inrb/5s50cRmrmtg== - dependencies: - "@lerna/check-working-tree" "5.5.2" - "@lerna/child-process" "5.5.2" - "@lerna/collect-updates" "5.5.2" - "@lerna/command" "5.5.2" - "@lerna/conventional-commits" "5.5.2" - "@lerna/github-client" "5.5.2" - "@lerna/gitlab-client" "5.5.2" - "@lerna/output" "5.5.2" - "@lerna/prerelease-id-from-version" "5.5.2" - "@lerna/prompt" "5.5.2" - "@lerna/run-lifecycle" "5.5.2" - "@lerna/run-topologically" "5.5.2" - "@lerna/temp-write" "5.5.2" - "@lerna/validation-error" "5.5.2" +"@lerna/version@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-6.4.1.tgz#01011364df04240ce92dffed1d2fa76bb9f959ff" + integrity sha512-1/krPq0PtEqDXtaaZsVuKev9pXJCkNC1vOo2qCcn6PBkODw/QTAvGcUi0I+BM2c//pdxge9/gfmbDo1lC8RtAQ== + dependencies: + "@lerna/check-working-tree" "6.4.1" + "@lerna/child-process" "6.4.1" + "@lerna/collect-updates" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/conventional-commits" "6.4.1" + "@lerna/github-client" "6.4.1" + "@lerna/gitlab-client" "6.4.1" + "@lerna/output" "6.4.1" + "@lerna/prerelease-id-from-version" "6.4.1" + "@lerna/prompt" "6.4.1" + "@lerna/run-lifecycle" "6.4.1" + "@lerna/run-topologically" "6.4.1" + "@lerna/temp-write" "6.4.1" + "@lerna/validation-error" "6.4.1" + "@nrwl/devkit" ">=15.4.2 < 16" chalk "^4.1.0" dedent "^0.7.0" load-json-file "^6.2.0" @@ -3042,10 +3043,10 @@ slash "^3.0.0" write-json-file "^4.3.0" -"@lerna/write-log-file@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-5.5.2.tgz#4ae8243b8e2821feea9f25c67488409a7fe82544" - integrity sha512-eeW10lriUl3w6WXtYk30z4rZB77QXeQCkLgSMv6Rqa7AMCTZNPhIBJQ0Nkmxo8LaFSWMhin1pLhHTYdqcsaFLA== +"@lerna/write-log-file@6.4.1": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-6.4.1.tgz#b9b959e4b853cdabf0309bc5da1513fa025117ec" + integrity sha512-LE4fueQSDrQo76F4/gFXL0wnGhqdG7WHVH8D8TrKouF2Afl4NHltObCm4WsSMPjcfciVnZQFfx1ruxU4r/enHQ== dependencies: npmlog "^6.0.2" write-file-atomic "^4.0.1" @@ -3426,19 +3427,30 @@ read-package-json-fast "^2.0.3" which "^2.0.2" -"@nrwl/cli@14.7.8": - version "14.7.8" - resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-14.7.8.tgz#8a2febb47ce7ea1aa63d090fbbf113c4447a85f2" - integrity sha512-QH1egjg4gSVOZXOOhECAx9c18d/TdeqhNeTw2skHww2G9IbUwgab+jqL6GSMPuuGtLs7Vagt2kUkc7aegNgUuA== +"@nrwl/cli@15.6.3": + version "15.6.3" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-15.6.3.tgz#999531d6efb30afc39373bdcbd7e78254a3a3fd3" + integrity sha512-K4E0spofThZXMnhA6R8hkUTdfqmwSnUE2+DlD5Y3jqsvKTAgwF5U41IFkEouFZCf+dWjy0RA20bWoX48EVFtmQ== dependencies: - nx "14.7.8" + nx "15.6.3" -"@nrwl/tao@14.7.8": - version "14.7.8" - resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-14.7.8.tgz#a9b83eb27b3f9c187efed429969d3ab798ad8ec1" - integrity sha512-pQ1eoesFKaEGWZLTAhv6Bs/2PS7GaT/jbT6ZN7ZhvYQq88DZxVb9SJkTthSaSJ22MHHevmljOeiv5onRffDsqQ== +"@nrwl/devkit@>=15.4.2 < 16": + version "15.6.3" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-15.6.3.tgz#e4e96c53ba3304786a49034286c8511534b2b194" + integrity sha512-/JDvdzNxUM+C1PCZPCrvmFx+OfywqZdOq1GS9QR8C0VctTLG4D/SGSFD88O1SAdcbH/f1mMiBGfEYZYd23fghQ== dependencies: - nx "14.7.8" + "@phenomnomnominal/tsquery" "4.1.1" + ejs "^3.1.7" + ignore "^5.0.4" + semver "7.3.4" + tslib "^2.3.0" + +"@nrwl/tao@15.6.3": + version "15.6.3" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-15.6.3.tgz#b24e11345375dea96bc386c60b9b1102a7584932" + integrity sha512-bDZbPIbU5Mf2BvX0q8GjPxrm1WkYyfW+gp7mLuuJth2sEpZiCr47mSwuGko/y4CKXvIX46VQcAS0pKQMKugXsg== + dependencies: + nx "15.6.3" "@oclif/color@^1.0.0": version "1.0.1" @@ -3818,6 +3830,13 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" +"@phenomnomnominal/tsquery@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz#42971b83590e9d853d024ddb04a18085a36518df" + integrity sha512-jjMmK1tnZbm1Jq5a7fBliM4gQwjxMU7TFoRNwIyzwlO+eHPRCFv/Nv+H/Gi1jc3WR7QURG8D5d0Tn12YGrUqBQ== + dependencies: + esquery "^1.0.1" + "@sinclair/typebox@^0.24.1": version "0.24.51" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" @@ -4304,6 +4323,26 @@ "@typescript-eslint/types" "5.46.0" eslint-visitor-keys "^3.3.0" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +"@yarnpkg/parsers@^3.0.0-rc.18": + version "3.0.0-rc.38" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.38.tgz#91b393554017016e12d2f4ea33f589dcfe7d5670" + integrity sha512-YqkUSOZSBjbhzvU/ZbK6yoE70L/KVXAQTyUMaKAFoHEpy7csAljivTBu0C3SZKbDxMRjFWAvnLS8US7W3hFLow== + dependencies: + js-yaml "^3.10.0" + tslib "^2.4.0" + +"@zkochan/js-yaml@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" + integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== + dependencies: + argparse "^2.0.1" + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -4457,7 +4496,7 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -4610,7 +4649,7 @@ aws-sdk@^2.1069.0: uuid "3.3.2" xml2js "0.4.19" -axios@0.21.4: +axios@0.21.4, axios@^1.0.0: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -4709,11 +4748,6 @@ bin-links@^3.0.0: rimraf "^3.0.0" write-file-atomic "^4.0.0" -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - binaryextensions@^4.15.0, binaryextensions@^4.16.0: version "4.18.0" resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-4.18.0.tgz#22aeada2d14de062c60e8ca59a504a5636a76ceb" @@ -4772,7 +4806,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -5014,14 +5048,6 @@ chai@4.2.0: pathval "^1.1.0" type-detect "^4.0.5" -chalk@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -5065,21 +5091,6 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -chokidar@^3.5.1: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -6078,7 +6089,7 @@ esprima@^4.0.0, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: +esquery@^1.0.1, esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== @@ -6427,10 +6438,10 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== +fs-extra@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" + integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -6476,7 +6487,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -6689,7 +6700,7 @@ github-username@^6.0.0: dependencies: "@octokit/rest" "^18.0.6" -glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.1, glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -7149,13 +7160,6 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -7229,7 +7233,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -7859,7 +7863,7 @@ jmespath@0.16.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.14.1, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.1: +js-yaml@3.14.1, js-yaml@^3.10.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -7941,10 +7945,15 @@ json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== -jsonc-parser@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" - integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== jsonfile@^4.0.0: version "4.0.0" @@ -7995,30 +8004,35 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lerna@5.5.2: - version "5.5.2" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-5.5.2.tgz#3b96ea81bb71a0e57c110b64c6dc2cc58d7acca9" - integrity sha512-P0ThZMfWJ4BP9xRbXaLyoOCYjlPN615FRV2ZBnTBA12lw32IlcREIgvF0N1zZX7wXtsmN56rU3CABoJ5lU8xuw== - dependencies: - "@lerna/add" "5.5.2" - "@lerna/bootstrap" "5.5.2" - "@lerna/changed" "5.5.2" - "@lerna/clean" "5.5.2" - "@lerna/cli" "5.5.2" - "@lerna/create" "5.5.2" - "@lerna/diff" "5.5.2" - "@lerna/exec" "5.5.2" - "@lerna/import" "5.5.2" - "@lerna/info" "5.5.2" - "@lerna/init" "5.5.2" - "@lerna/link" "5.5.2" - "@lerna/list" "5.5.2" - "@lerna/publish" "5.5.2" - "@lerna/run" "5.5.2" - "@lerna/version" "5.5.2" +lerna@6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-6.4.1.tgz#a1e5abcb6c00de3367f50d75eca449e382525e0f" + integrity sha512-0t8TSG4CDAn5+vORjvTFn/ZEGyc4LOEsyBUpzcdIxODHPKM4TVOGvbW9dBs1g40PhOrQfwhHS+3fSx/42j42dQ== + dependencies: + "@lerna/add" "6.4.1" + "@lerna/bootstrap" "6.4.1" + "@lerna/changed" "6.4.1" + "@lerna/clean" "6.4.1" + "@lerna/cli" "6.4.1" + "@lerna/command" "6.4.1" + "@lerna/create" "6.4.1" + "@lerna/diff" "6.4.1" + "@lerna/exec" "6.4.1" + "@lerna/filter-options" "6.4.1" + "@lerna/import" "6.4.1" + "@lerna/info" "6.4.1" + "@lerna/init" "6.4.1" + "@lerna/link" "6.4.1" + "@lerna/list" "6.4.1" + "@lerna/publish" "6.4.1" + "@lerna/run" "6.4.1" + "@lerna/validation-error" "6.4.1" + "@lerna/version" "6.4.1" + "@nrwl/devkit" ">=15.4.2 < 16" import-local "^3.0.2" + inquirer "^8.2.4" npmlog "^6.0.2" - nx ">=14.6.1 < 16" + nx ">=15.4.2 < 16" typescript "^3 || ^4" level-concat-iterator@~2.0.0: @@ -8108,6 +8122,11 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +lines-and-columns@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b" + integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -8451,6 +8470,11 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -8808,7 +8832,7 @@ normalize-package-data@^4.0.0: semver "^7.3.5" validate-npm-package-license "^3.0.4" -normalize-path@^3.0.0, normalize-path@~3.0.0: +normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -9003,16 +9027,19 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -nx@14.7.8, "nx@>=14.6.1 < 16": - version "14.7.8" - resolved "https://registry.yarnpkg.com/nx/-/nx-14.7.8.tgz#89348d0161c967c2122b42d8db0cf0454e88c255" - integrity sha512-fSnjS7R1iB9ZtsZ4HPkt/xwl8Z+SfgXY6bH99LCAL2KniaMxbnoU5S3N+WbIZb6KnXQjl/rCxTZYoQaRL7C8pQ== +nx@15.6.3, "nx@>=15.4.2 < 16": + version "15.6.3" + resolved "https://registry.yarnpkg.com/nx/-/nx-15.6.3.tgz#900087bce38c6e5975660c23ebd41ead1bf54f98" + integrity sha512-3t0A0GPLNen1yPAyE+VGZ3nkAzZYb5nfXtAcx8SHBlKq4u42yBY3khBmP1y4Og3jhIwFIj7J7Npeh8ZKrthmYQ== dependencies: - "@nrwl/cli" "14.7.8" - "@nrwl/tao" "14.7.8" + "@nrwl/cli" "15.6.3" + "@nrwl/tao" "15.6.3" "@parcel/watcher" "2.0.4" - chalk "4.1.0" - chokidar "^3.5.1" + "@yarnpkg/lockfile" "^1.1.0" + "@yarnpkg/parsers" "^3.0.0-rc.18" + "@zkochan/js-yaml" "0.0.6" + axios "^1.0.0" + chalk "^4.1.0" cli-cursor "3.1.0" cli-spinners "2.6.1" cliui "^7.0.2" @@ -9021,23 +9048,25 @@ nx@14.7.8, "nx@>=14.6.1 < 16": fast-glob "3.2.7" figures "3.2.0" flat "^5.0.2" - fs-extra "^10.1.0" + fs-extra "^11.1.0" glob "7.1.4" ignore "^5.0.4" js-yaml "4.1.0" - jsonc-parser "3.0.0" + jsonc-parser "3.2.0" + lines-and-columns "~2.0.3" minimatch "3.0.5" npm-run-path "^4.0.1" open "^8.4.0" semver "7.3.4" string-width "^4.2.3" + strong-log-transformer "^2.1.0" tar-stream "~2.2.0" tmp "~0.2.1" - tsconfig-paths "^3.9.0" + tsconfig-paths "^4.1.2" tslib "^2.3.0" v8-compile-cache "2.3.0" - yargs "^17.4.0" - yargs-parser "21.0.1" + yargs "^17.6.2" + yargs-parser "21.1.1" object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" @@ -9461,11 +9490,6 @@ picomatch@^2.0.4, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== -picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -9856,13 +9880,6 @@ readdir-scoped-modules@^1.1.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -10822,6 +10839,15 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" +tsconfig-paths@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz#4819f861eef82e6da52fb4af1e8c930a39ed979a" + integrity sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -10837,6 +10863,11 @@ tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@^2.4.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tslib@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" @@ -11319,21 +11350,16 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@21.0.1: - version "21.0.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" - integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== +yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.0, yargs-parser@^21.0.1, yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" @@ -11347,7 +11373,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.3.1: +yargs@^17.3.1, yargs@^17.6.2: version "17.6.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== @@ -11360,19 +11386,6 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" -yargs@^17.4.0: - version "17.5.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - yarn@^1.22.10: version "1.22.17" resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.17.tgz#bf910747d22497b573131f7341c0e1d15c74036c"