From 01fd20996a65b9d417b3a15dbfd08f522a584059 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Mon, 20 Mar 2023 13:56:58 -0700 Subject: [PATCH 01/21] chore(ironfish): remove unused/duplicate utils.ts file (#3673) This file is exactly the same as `ironfish/src/rpc/routes/chain/utils.ts`, and is unused. If we need to re-add this functional for whatever reason, we should relocate the utils.ts file in chain by moving it up a level or something. --- ironfish/src/rpc/routes/events/utils.ts | 161 ------------------------ 1 file changed, 161 deletions(-) delete mode 100644 ironfish/src/rpc/routes/events/utils.ts diff --git a/ironfish/src/rpc/routes/events/utils.ts b/ironfish/src/rpc/routes/events/utils.ts deleted file mode 100644 index 07c6f661f0..0000000000 --- a/ironfish/src/rpc/routes/events/utils.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { BufferSet } from 'buffer-map' -import { Assert } from '../../../assert' -import { Blockchain } from '../../../blockchain' -import { VerificationResult } from '../../../consensus' -import { createRootLogger, Logger } from '../../../logger' -import { BlockHeader } from '../../../primitives/blockheader' -import { HashUtils } from '../../../utils' - -const DEFAULT_OPTIONS = { - seq: true, - work: true, - indent: '|', -} - -export async function logChain( - chain: Blockchain, - start?: number | null, - end?: number | null, - options: { - prev?: boolean - merge?: boolean - seq?: boolean - work?: boolean - indent?: string - } = DEFAULT_OPTIONS, - logger?: Logger, -): Promise { - const content = await renderChain(chain, start, end, options, logger) - - if (logger) { - logger.info(content.join('\n')) - } else { - // eslint-disable-next-line no-console - console.log(content.join('\n')) - } -} - -export async function renderChain( - chain: Blockchain, - start?: number | null, - end?: number | null, - options: { - prev?: boolean - seq?: boolean - work?: boolean - indent?: string - } = DEFAULT_OPTIONS, - logger = createRootLogger(), -): Promise { - const content: string[] = [] - - let trees: VerificationResult = { valid: true } - if (chain.head) { - const headBlock = await chain.getBlock(chain.head) - Assert.isNotNull(headBlock) - trees = await chain.verifier.verifyConnectedBlock(headBlock) - } - - content.push( - '======', - `GENESIS: ${chain.genesis.hash.toString('hex') || '-'}`, - `HEAD: ${chain.head.hash.toString('hex') || '-'}`, - `LATEST: ${chain.latest.hash.toString('hex') || '-'}`, - `TREES: ${trees.valid ? 'OK' : `ERROR: ${String(trees.reason)}`}`, - '======', - ) - - start = start || chain.genesis.sequence - end = end || chain.latest.sequence - - const roots = await chain.getHeadersAtSequence(start) - - for (const root of roots) { - await renderGraph(chain, root, end, content, options, logger) - } - - return content -} - -export async function renderGraph( - chain: Blockchain, - header: BlockHeader, - end: number, - content: string[], - options: { - prev?: boolean - seq?: boolean - work?: boolean - indent?: string - } = DEFAULT_OPTIONS, - logger = createRootLogger(), - last = true, - _only = true, - indent = '', - seen = new BufferSet(), -): Promise { - Assert.isNotNull(chain.latest) - Assert.isNotNull(chain.head) - Assert.isNotNull(chain.genesis) - - seen.add(header.hash) - - let rendered = `+- Block ${HashUtils.renderHash(header.hash)}` - - if (options.seq) { - rendered += ` (${header.sequence})` - } - if (options.prev) { - rendered += ` prev: ${HashUtils.renderHash(header.previousBlockHash)}` - } - if (options.work) { - rendered += ` work: ${header.work.toString()}` - } - - if (header.hash.equals(chain.latest.hash)) { - rendered += ' LATEST' - } - if (header.hash.equals(chain.head.hash)) { - rendered += ' HEAD' - } - if (header.hash.equals(chain.genesis.hash)) { - rendered += ' GENESIS' - } - - content.push(indent + rendered) - - if (header.sequence === end) { - return - } - - const next = await chain.getHeadersAtSequence(header.sequence + 1) - const children = next.filter((h) => h.previousBlockHash.equals(header.hash)) - const nesting = children.length >= 2 - - const indentation = nesting ? options.indent || '' : '' - indent += last ? indentation : `| ${indentation}` - - for (let i = 0; i < children.length; i++) { - const child = children[i] - - if (seen.has(child.hash)) { - logger.error( - `ERROR FOUND LOOPING CHAIN ${header.hash.toString('hex')} -> ${child.hash.toString( - 'hex', - )}`, - ) - return - } - - const last = i === children.length - 1 - const only = children.length === 1 - await renderGraph(chain, child, end, content, options, logger, last, only, indent, seen) - - if (!last) { - content.push(indent + '') - } - } -} From 6cdf05d9d2e1efd92f78b4ac60248c491108781c Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Mon, 20 Mar 2023 16:20:06 -0700 Subject: [PATCH 02/21] Add get contributions command for admin users (#3650) --- .../src/commands/ceremony/contributions.ts | 65 +++++++++++++++++++ .../{ceremony.ts => ceremony/index.ts} | 6 +- .../ceremony.ts => ceremony/service.ts} | 4 +- ironfish-cli/src/utils/s3.ts | 12 ++++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 ironfish-cli/src/commands/ceremony/contributions.ts rename ironfish-cli/src/commands/{ceremony.ts => ceremony/index.ts} (98%) rename ironfish-cli/src/commands/{service/ceremony.ts => ceremony/service.ts} (96%) diff --git a/ironfish-cli/src/commands/ceremony/contributions.ts b/ironfish-cli/src/commands/ceremony/contributions.ts new file mode 100644 index 0000000000..0603987f5c --- /dev/null +++ b/ironfish-cli/src/commands/ceremony/contributions.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 { S3Client } from '@aws-sdk/client-s3' +import { Flags } from '@oclif/core' +import { IronfishCommand } from '../../command' +import { S3Utils } from '../../utils' + +export default class CeremonyContributions extends IronfishCommand { + static description = 'List all the current contributions with names' + + static flags = { + start: Flags.integer({ + required: false, + }), + end: Flags.integer({ + required: false, + }), + } + + async start(): Promise { + const { flags } = await this.parse(CeremonyContributions) + + const r2Credentials = await S3Utils.getR2Credentials() + + if (r2Credentials === undefined) { + this.logger.log('Failed getting R2 credentials from AWS') + this.exit(0) + return + } + + const r2Client = S3Utils.getR2S3Client(r2Credentials) + + const latestParamName = await this.getLatestParamName(r2Client, 'ironfish-contributions') + const latestParamNumber = parseInt(latestParamName.split('_')[1]) + const keys: string[] = [...new Array(latestParamNumber + 1)] + .map((_, i) => i) + .filter((i) => (!flags.start || i >= flags.start) && (!flags.end || i <= flags.end)) + .map((i) => { + return 'params_' + i.toString().padStart(5, '0') + }) + + for (const key of keys) { + const { Metadata } = await S3Utils.getObjectMetadata( + r2Client, + 'ironfish-contributions', + key, + ) + this.log( + `Contribution: ${key.split('_')[1]}, Name: ${Metadata?.contributorName || '-'}, IP: ${ + Metadata?.remoteaddress || '-' + }`, + ) + } + } + + async getLatestParamName(client: S3Client, bucket: string): Promise { + const paramFileNames = await S3Utils.getBucketObjects(client, bucket) + const validParams = paramFileNames + .slice(0) + .filter((fileName) => /^params_\d{5}$/.test(fileName)) + validParams.sort() + return validParams[validParams.length - 1] + } +} diff --git a/ironfish-cli/src/commands/ceremony.ts b/ironfish-cli/src/commands/ceremony/index.ts similarity index 98% rename from ironfish-cli/src/commands/ceremony.ts rename to ironfish-cli/src/commands/ceremony/index.ts index 53cdf21762..ab24059f59 100644 --- a/ironfish-cli/src/commands/ceremony.ts +++ b/ironfish-cli/src/commands/ceremony/index.ts @@ -8,9 +8,9 @@ import axios from 'axios' import fsAsync from 'fs/promises' import path from 'path' import { pipeline } from 'stream/promises' -import { IronfishCommand } from '../command' -import { DataDirFlag, DataDirFlagKey, VerboseFlag, VerboseFlagKey } from '../flags' -import { CeremonyClient } from '../trusted-setup/client' +import { IronfishCommand } from '../../command' +import { DataDirFlag, DataDirFlagKey, VerboseFlag, VerboseFlagKey } from '../../flags' +import { CeremonyClient } from '../../trusted-setup/client' export default class Ceremony extends IronfishCommand { static description = 'Contribute randomness to the Iron Fish trusted setup' diff --git a/ironfish-cli/src/commands/service/ceremony.ts b/ironfish-cli/src/commands/ceremony/service.ts similarity index 96% rename from ironfish-cli/src/commands/service/ceremony.ts rename to ironfish-cli/src/commands/ceremony/service.ts index f327301356..b5991d6bbe 100644 --- a/ironfish-cli/src/commands/service/ceremony.ts +++ b/ironfish-cli/src/commands/ceremony/service.ts @@ -13,7 +13,7 @@ const UPLOAD_TIMEOUT_MS = 5 * 60 * 1000 const PRESIGNED_EXPIRATION_SEC = 5 * 60 const START_DATE = 1681146000000 // Monday, April 10, 2023 10:00:00 AM GMT-07:00 (Pacific Daylight Time) -export default class Ceremony extends IronfishCommand { +export default class CeremonyService extends IronfishCommand { static hidden = true static description = ` @@ -68,7 +68,7 @@ export default class Ceremony extends IronfishCommand { } async start(): Promise { - const { flags } = await this.parse(Ceremony) + const { flags } = await this.parse(CeremonyService) const DEFAULT_HOST = '0.0.0.0' const DEFAULT_PORT = 9040 diff --git a/ironfish-cli/src/utils/s3.ts b/ironfish-cli/src/utils/s3.ts index 151b8b954f..3bde79813b 100644 --- a/ironfish-cli/src/utils/s3.ts +++ b/ironfish-cli/src/utils/s3.ts @@ -11,6 +11,8 @@ import { DeleteObjectCommand, DeleteObjectCommandOutput, GetObjectCommand, + HeadObjectCommand, + HeadObjectCommandOutput, ListObjectsCommand, ListObjectsCommandInput, PutObjectCommand, @@ -263,6 +265,16 @@ export function getDownloadUrl( return `https://${bucket}.${regionString}.amazonaws.com/${key}` } +export async function getObjectMetadata( + s3: S3Client, + bucket: string, + key: string, +): Promise { + const command = new HeadObjectCommand({ Bucket: bucket, Key: key }) + const response = await s3.send(command) + return response +} + export async function getBucketObjects(s3: S3Client, bucket: string): Promise { let truncated = true let commandParams: ListObjectsCommandInput = { Bucket: bucket } From 0dca24b2b144a3f4b03b842cd235b4c74cfff5f9 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Tue, 21 Mar 2023 10:09:31 -0700 Subject: [PATCH 03/21] chore(ironfish): update sqlite3 (#3674) --- ironfish/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ironfish/package.json b/ironfish/package.json index 97d9f3dca5..710e1e74e5 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -40,7 +40,7 @@ "node-forge": "1.3.1", "parse-json": "5.2.0", "sqlite": "4.0.23", - "sqlite3": "5.0.4", + "sqlite3": "5.1.6", "uuid": "8.3.2", "ws": "8.12.1", "yup": "0.29.3" diff --git a/yarn.lock b/yarn.lock index 125daaf905..c93786475b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10947,10 +10947,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sqlite3@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.0.4.tgz#3ddff8d360dab3f17c596690d8663f353d876187" - integrity sha512-ATvAe7JutFv/d+KTbLS58KsKn/t1raL/WGn2qZfZxwsrL/oGSP+0OlbQ2tX5jISvyu6/7JuKze3WkaiP1JAH6A== +sqlite3@5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.6.tgz#1d4fbc90fe4fbd51e952e0a90fd8f6c2b9098e97" + integrity sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw== dependencies: "@mapbox/node-pre-gyp" "^1.0.0" node-addon-api "^4.2.0" From a38b28eadb8b2081d529356dcedb1a80992aaf77 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:01:32 -0700 Subject: [PATCH 04/21] enforces enums in wallet transaction rpc schemas (#3680) the getAccountTransaction and getAccountTransactions RPC endpoints return a transaction status and a transaction type with each transaction in a response. each of these fields has a set of valid values that we define with an enum. the response schemas, however, only specify that 'status' and 'type' are strings. updates the response schemas to enforce that transaction status is one of the TransactionStatus enum values and that transaction type is one of the TransactionType enum values. --- ironfish/src/rpc/routes/wallet/getTransaction.ts | 9 +++++---- ironfish/src/rpc/routes/wallet/getTransactions.ts | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/getTransaction.ts b/ironfish/src/rpc/routes/wallet/getTransaction.ts index e11bf1ace1..b0b986f221 100644 --- a/ironfish/src/rpc/routes/wallet/getTransaction.ts +++ b/ironfish/src/rpc/routes/wallet/getTransaction.ts @@ -4,6 +4,7 @@ import * as yup from 'yup' import { Note } from '../../../primitives/note' import { CurrencyUtils } from '../../../utils' +import { TransactionStatus, TransactionType } from '../../../wallet' import { ApiNamespace, router } from '../router' import { getAssetBalanceDeltas, @@ -22,8 +23,8 @@ export type GetAccountTransactionResponse = { account: string transaction: { hash: string - status: string - type: string + status: TransactionStatus + type: TransactionType fee: string blockHash?: string blockSequence?: number @@ -53,8 +54,8 @@ export const GetAccountTransactionResponseSchema: yup.ObjectSchema = yup .object({ - status: yup.string().defined(), - type: yup.string().defined(), + status: yup.string().oneOf(Object.values(TransactionStatus)).defined(), + type: yup.string().oneOf(Object.values(TransactionType)).defined(), hash: yup.string().defined(), fee: yup.string().defined(), notesCount: yup.number().defined(), From 3bd97582d87346c6ea45fdf56b8b382f6ee99dd3 Mon Sep 17 00:00:00 2001 From: Evan Richard <5766842+EvanJRichard@users.noreply.github.com> Date: Thu, 23 Mar 2023 11:32:28 -0400 Subject: [PATCH 05/21] Call forceCleanupDeletedAccounts at end of wallet:prune (#3676) Review: https://github.com/iron-fish/ironfish/pull/3676 --- ironfish-cli/src/commands/wallet/prune.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ironfish-cli/src/commands/wallet/prune.ts b/ironfish-cli/src/commands/wallet/prune.ts index 38fbcebc1f..5e2e25e94a 100644 --- a/ironfish-cli/src/commands/wallet/prune.ts +++ b/ironfish-cli/src/commands/wallet/prune.ts @@ -65,6 +65,10 @@ export default class PruneCommand extends IronfishCommand { } } + CliUx.ux.action.start(`Cleaning up deleted accounts`) + await node.wallet.forceCleanupDeletedAccounts() + CliUx.ux.action.stop() + if (flags.compact) { CliUx.ux.action.start(`Compacting wallet database`) await node.wallet.walletDb.db.compact() From e90d4a01b17dba1e1bde177423acd4686319f3c9 Mon Sep 17 00:00:00 2001 From: Evan Richard <5766842+EvanJRichard@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:10:10 -0400 Subject: [PATCH 06/21] Update httpAdapter: no requirement that a caller use POST (#3669) Review: https://github.com/iron-fish/ironfish/pull/3669 --- ironfish/src/rpc/adapters/httpAdapter.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ironfish/src/rpc/adapters/httpAdapter.ts b/ironfish/src/rpc/adapters/httpAdapter.ts index ee096582a2..c26f3a89a0 100644 --- a/ironfish/src/rpc/adapters/httpAdapter.ts +++ b/ironfish/src/rpc/adapters/httpAdapter.ts @@ -170,14 +170,6 @@ export class RpcHttpAdapter implements IRpcAdapter { const url = new URL(request.url, `http://${request.headers.host || 'localhost'}`) const route = url.pathname.substring(1) - if (request.method !== 'POST') { - throw new ResponseError( - `Route does not exist, Did you mean to use POST?`, - ERROR_CODES.ROUTE_NOT_FOUND, - 404, - ) - } - // TODO(daniel): clean up reading body code here a bit of possible let size = 0 const data: Buffer[] = [] From 4d33cf5323b225a75ee7490b083247e3b210fd51 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:31:22 -0700 Subject: [PATCH 07/21] fixes yup currency validation with min (#3688) * fixes yup currency validation with min when using our custom 'currency' yup schema with a minimum value we do not catch errors from 'CurrencyUtils.decode'. this results in a non-yup error during validation if the value cannot be decoded. an error other than 'yup.ValidationError' in an RPC endpoint crashes the node. see #3662 * restores unique error message for min updates tests expecting validation error message --- ironfish/src/rpc/routes/wallet/burnAsset.test.ts | 8 ++++++-- ironfish/src/rpc/routes/wallet/mintAsset.test.ts | 8 ++++++-- ironfish/src/utils/yup.test.ts | 1 + ironfish/src/utils/yup.ts | 11 +++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ironfish/src/rpc/routes/wallet/burnAsset.test.ts b/ironfish/src/rpc/routes/wallet/burnAsset.test.ts index da97790260..32c19db60f 100644 --- a/ironfish/src/rpc/routes/wallet/burnAsset.test.ts +++ b/ironfish/src/rpc/routes/wallet/burnAsset.test.ts @@ -26,7 +26,9 @@ describe('burnAsset', () => { fee: '0', value: '100', }), - ).rejects.toThrow('value must be equal to or greater than 1') + ).rejects.toThrow( + 'Request failed (400) validation: value must be equal to or greater than 1', + ) }) }) @@ -39,7 +41,9 @@ describe('burnAsset', () => { fee: '1', value: '-1', }), - ).rejects.toThrow('value must be equal to or greater than 1') + ).rejects.toThrow( + 'Request failed (400) validation: value must be equal to or greater than 1', + ) }) }) diff --git a/ironfish/src/rpc/routes/wallet/mintAsset.test.ts b/ironfish/src/rpc/routes/wallet/mintAsset.test.ts index 41fdeaf77a..221034444f 100644 --- a/ironfish/src/rpc/routes/wallet/mintAsset.test.ts +++ b/ironfish/src/rpc/routes/wallet/mintAsset.test.ts @@ -23,7 +23,9 @@ describe('mint', () => { name: 'fake-coin', value: '100', }), - ).rejects.toThrow('value must be equal to or greater than 1') + ).rejects.toThrow( + 'Request failed (400) validation: value must be equal to or greater than 1', + ) }) }) @@ -37,7 +39,9 @@ describe('mint', () => { name: 'fake-coin', value: '-1', }), - ).rejects.toThrow('value must be equal to or greater than 1') + ).rejects.toThrow( + 'Request failed (400) validation: value must be equal to or greater than 1', + ) }) }) diff --git a/ironfish/src/utils/yup.test.ts b/ironfish/src/utils/yup.test.ts index da8833f291..1789b4d8fe 100644 --- a/ironfish/src/utils/yup.test.ts +++ b/ironfish/src/utils/yup.test.ts @@ -35,6 +35,7 @@ describe('YupUtils', () => { it('currency', () => { expect(YupUtils.currency().isValidSync(CurrencyUtils.encode(6n))).toBe(true) expect(YupUtils.currency({ min: 0n }).isValidSync(CurrencyUtils.encode(-1n))).toBe(false) + expect(YupUtils.currency({ min: 0n }).isValidSync('0.1')).toBe(false) expect(YupUtils.currency().isValidSync('hello world')).toBe(false) expect(YupUtils.currency().isValidSync(0.00046)).toBe(false) }) diff --git a/ironfish/src/utils/yup.ts b/ironfish/src/utils/yup.ts index b04113e692..b8a63f004e 100644 --- a/ironfish/src/utils/yup.ts +++ b/ironfish/src/utils/yup.ts @@ -36,9 +36,16 @@ export class YupUtils { const min = options?.min schema = schema.test( - `min`, + 'min', `value must be equal to or greater than ${min.toString()}`, - (val) => val == null || CurrencyUtils.decode(val) >= min, + (val) => { + if (val == null) { + return true + } + + const [value] = CurrencyUtils.decodeTry(val) + return value != null && value >= min + }, ) } From eeeeb30f47ded8293f3976610058fe0df090523c Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Thu, 23 Mar 2023 16:40:12 -0700 Subject: [PATCH 08/21] Tag docker deploys with Github info (#3689) --- .github/workflows/deploy-node-aws.yml | 2 ++ .github/workflows/deploy-node-github-beta.yml | 2 ++ .github/workflows/deploy-node-github.yml | 2 ++ ironfish-cli/scripts/deploy-docker.sh | 13 ++++++++++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-node-aws.yml b/.github/workflows/deploy-node-aws.yml index 4ddb6a1ab6..4342100929 100644 --- a/.github/workflows/deploy-node-aws.yml +++ b/.github/workflows/deploy-node-aws.yml @@ -30,3 +30,5 @@ jobs: env: REGISTRY_URL: ${{ secrets.AWS_NODE_REGISTRY_URL }} PACKAGE_NAME: ironfish + GITHUB_REF: ${GITHUB_REF} + GITHUB_SHA: ${GITHUB_SHA} diff --git a/.github/workflows/deploy-node-github-beta.yml b/.github/workflows/deploy-node-github-beta.yml index 827ae85bbd..909aeb8b27 100644 --- a/.github/workflows/deploy-node-github-beta.yml +++ b/.github/workflows/deploy-node-github-beta.yml @@ -28,3 +28,5 @@ jobs: env: REGISTRY_URL: ghcr.io/iron-fish PACKAGE_NAME: ironfish-beta + GITHUB_REF: "$GITHUB_REF" + GITHUB_SHA: "$GITHUB_SHA" diff --git a/.github/workflows/deploy-node-github.yml b/.github/workflows/deploy-node-github.yml index b8cde49e28..f06b20a42e 100644 --- a/.github/workflows/deploy-node-github.yml +++ b/.github/workflows/deploy-node-github.yml @@ -28,3 +28,5 @@ jobs: env: REGISTRY_URL: ghcr.io/iron-fish PACKAGE_NAME: ironfish + GITHUB_REF: ${GITHUB_REF} + GITHUB_SHA: ${GITHUB_SHA} diff --git a/ironfish-cli/scripts/deploy-docker.sh b/ironfish-cli/scripts/deploy-docker.sh index 0abbe01110..3cea2277e6 100755 --- a/ironfish-cli/scripts/deploy-docker.sh +++ b/ironfish-cli/scripts/deploy-docker.sh @@ -12,7 +12,18 @@ if [ -z "${PACKAGE_NAME-}" ]; then exit 1 fi +if [ -z "${GITHUB_SHA-}" ]; then + echo "Set GITHUB_SHA before running deploy-docker.sh" + exit 1 +fi + +if [ -z "${GITHUB_REF-}" ]; then + echo "Set GITHUB_REF before running deploy-docker.sh" + exit 1 +fi docker tag ironfish:latest ${REGISTRY_URL}/${PACKAGE_NAME}:latest -docker push ${REGISTRY_URL}/${PACKAGE_NAME}:latest +docker tag ironfish:latest ${REGISTRY_URL}/${PACKAGE_NAME}:${GITHUB_REF} +docker tag ironfish:latest ${REGISTRY_URL}/${PACKAGE_NAME}:${GITHUB_SHA} +docker push --all-tags ${REGISTRY_URL}/${PACKAGE_NAME} From 8b539889be81977bc97b4fd21a088fcc0efd4df0 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Thu, 23 Mar 2023 17:41:24 -0700 Subject: [PATCH 09/21] Fix GitHub branch ref (#3691) --- .github/workflows/deploy-node-aws.yml | 2 +- .github/workflows/deploy-node-github-beta.yml | 2 +- .github/workflows/deploy-node-github.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-node-aws.yml b/.github/workflows/deploy-node-aws.yml index 4342100929..60048fe360 100644 --- a/.github/workflows/deploy-node-aws.yml +++ b/.github/workflows/deploy-node-aws.yml @@ -30,5 +30,5 @@ jobs: env: REGISTRY_URL: ${{ secrets.AWS_NODE_REGISTRY_URL }} PACKAGE_NAME: ironfish - GITHUB_REF: ${GITHUB_REF} + GITHUB_REF: ${GITHUB_REF_NAME} GITHUB_SHA: ${GITHUB_SHA} diff --git a/.github/workflows/deploy-node-github-beta.yml b/.github/workflows/deploy-node-github-beta.yml index 909aeb8b27..d59f7dfb1e 100644 --- a/.github/workflows/deploy-node-github-beta.yml +++ b/.github/workflows/deploy-node-github-beta.yml @@ -28,5 +28,5 @@ jobs: env: REGISTRY_URL: ghcr.io/iron-fish PACKAGE_NAME: ironfish-beta - GITHUB_REF: "$GITHUB_REF" + GITHUB_REF: ${GITHUB_REF_NAME} GITHUB_SHA: "$GITHUB_SHA" diff --git a/.github/workflows/deploy-node-github.yml b/.github/workflows/deploy-node-github.yml index f06b20a42e..9a7534fc89 100644 --- a/.github/workflows/deploy-node-github.yml +++ b/.github/workflows/deploy-node-github.yml @@ -28,5 +28,5 @@ jobs: env: REGISTRY_URL: ghcr.io/iron-fish PACKAGE_NAME: ironfish - GITHUB_REF: ${GITHUB_REF} + GITHUB_REF: ${GITHUB_REF_NAME} GITHUB_SHA: ${GITHUB_SHA} From 357e1eb29fa20745d1f2a9c901bc57d7e1b1fa38 Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:13:46 -0700 Subject: [PATCH 10/21] feat(cli): reset CLI command takes networkId flag and only modifies if given (#3690) * feat(cli): reset CLI command takes networkId flag and only modifies if given * show a message for when it is unchanged --- ironfish-cli/src/commands/reset.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ironfish-cli/src/commands/reset.ts b/ironfish-cli/src/commands/reset.ts index 69cedf0783..68b28f8152 100644 --- a/ironfish-cli/src/commands/reset.ts +++ b/ironfish-cli/src/commands/reset.ts @@ -21,6 +21,11 @@ export default class Reset extends IronfishCommand { [VerboseFlagKey]: VerboseFlag, [ConfigFlagKey]: ConfigFlag, [DataDirFlagKey]: DataDirFlag, + networkId: Flags.integer({ + char: 'i', + default: undefined, + description: 'Network ID of an official Iron Fish network to connect to', + }), confirm: Flags.boolean({ default: false, description: 'Confirm without asking', @@ -38,11 +43,21 @@ export default class Reset extends IronfishCommand { HOST_FILE_NAME, ) + const existingId = this.sdk.internal.get('networkId') + + let networkIdMessage = '' + if (flags.networkId != null && flags.networkId !== existingId) { + networkIdMessage = `\n\nThe network ID will be changed from ${existingId} to the new value of ${flags.networkId}` + } else { + networkIdMessage = `\n\nThe network ID will stay unchanged as ${existingId}` + } + const message = '\nYou are about to destroy your local copy of the blockchain. The following directories and files will be deleted:\n' + `\nBlockchain: ${chainDatabasePath}` + `\nHosts: ${hostFilePath}` + '\nYour wallet, accounts, and keys will NOT be deleted.' + + networkIdMessage + `\n\nAre you sure? (Y)es / (N)o` const confirmed = flags.confirm || (await CliUx.ux.confirm(message)) @@ -59,7 +74,9 @@ export default class Reset extends IronfishCommand { fsAsync.rm(hostFilePath, { recursive: true, force: true }), ]) - this.sdk.internal.set('networkId', this.sdk.config.defaults.networkId) + if (flags.networkId != null && flags.networkId !== existingId) { + this.sdk.internal.set('networkId', flags.networkId) + } this.sdk.internal.set('isFirstRun', true) await this.sdk.internal.save() From fe6995cf81d73915a4578dd91da0828538c0c489 Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 24 Mar 2023 13:02:35 -0700 Subject: [PATCH 11/21] set default getFundsApi to testnet domain (#3694) we've migrated the faucet to 'testnet.api.ironfish.network'. since the faucet will only ever work on the testnet, and never on mainnet, the default config should point to the testnet domain. --- ironfish/src/fileStores/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ironfish/src/fileStores/config.ts b/ironfish/src/fileStores/config.ts index 10989a78f3..6594c2b5f4 100644 --- a/ironfish/src/fileStores/config.ts +++ b/ironfish/src/fileStores/config.ts @@ -381,7 +381,7 @@ export class Config extends KeyStore { enableSyncing: true, enableTelemetry: false, enableMetrics: true, - getFundsApi: 'https://api.ironfish.network/faucet_transactions', + getFundsApi: 'https://testnet.api.ironfish.network/faucet_transactions', ipcPath: files.resolve(files.join(dataDir, 'ironfish.ipc')), logLevel: '*:info', logPeerMessages: false, From e0e9d4108d92708d5246f65883c356e87e4b0cda Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Fri, 24 Mar 2023 14:15:31 -0700 Subject: [PATCH 12/21] removes account hasSpend method (#3681) when adding a transaction to the wallet we use 'hasSpend' to iterate over all spends in the transaction to see whether the account spent any notes in the transaction before using 'addPendingTransaction' or 'connectTransaction'. however, both of those methods _also_ iterate over all spends in the transaction. this can result in unnecessary, duplicative iteration over spends. there is another drawback to the way we currently use 'hasSpend': the account 'addPendingTransaction' and 'connectTransaction' methods depend on the caller not to call them with any account and transaction where the account was not involved in the transaction. - removes 'hasSpend' method - does not check 'hasSpend' at wallet level before adding transaction to account - updates account 'addPendingTransaction' and 'connectTransaction' methods to return early if the account did not receive or spend any notes in the transaction --- .../__fixtures__/account.test.ts.fixture | 54 +++++++++++++++++++ .../__fixtures__/wallet.test.ts.fixture | 54 ------------------- ironfish/src/wallet/account.test.ts | 19 +++++++ ironfish/src/wallet/account.ts | 20 +++---- ironfish/src/wallet/wallet.test.ts | 22 -------- ironfish/src/wallet/wallet.ts | 8 --- 6 files changed, 83 insertions(+), 94 deletions(-) diff --git a/ironfish/src/wallet/__fixtures__/account.test.ts.fixture b/ironfish/src/wallet/__fixtures__/account.test.ts.fixture index 79ce83ef81..732a59eb4f 100644 --- a/ironfish/src/wallet/__fixtures__/account.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/account.test.ts.fixture @@ -3721,5 +3721,59 @@ } ] } + ], + "Accounts addPendingTransaction should only save transactions to accounts involved in the transaction": [ + { + "version": 1, + "id": "b532d80b-e581-4a61-bb3a-531fd590ca2c", + "name": "a", + "spendingKey": "15bcd47611f48cc2449626304d9a06207bbb6deec1921418002a59e3ac8d53c2", + "viewKey": "91f2958e4337a1d12b678f56d4a2199048d5d84b325ca5f3b1dbd3c4abf5f640e0acb979830c314c781f211ca6f68c8c77000ba7e4fa7cdf2299cbd96ccac21b", + "incomingViewKey": "f1d9fe8822480109f6bcbb29d595d6f3dba7e396a6a09632573310d88dcb7f06", + "outgoingViewKey": "93a1c3e4865629efc480d8728fef97532fdcaab6ea41249238bac11ff488a049", + "publicAddress": "6b46e8def47e285e9d70df20cd52a816894218b2385cd40d99b1588c3d011c97", + "createdAt": "2023-03-22T18:45:17.951Z" + }, + { + "version": 1, + "id": "dc785412-7637-456d-b191-db3e9ee2ec41", + "name": "b", + "spendingKey": "08f7d74adeedda62bc1cbf74bd1a91d4838143eb6e273ac4dec0e55fcdb0df11", + "viewKey": "912039eead30827067ff2c54840f440317cc982bd8ef538b6fdd473fe46995cdc5fb40e60250f8e8585ff5b7ae5a5eb0032bd76b661c09ce7fa9b822f2914470", + "incomingViewKey": "843322669aecc1e9850e873e703fb5786e101d10d3e02c482ee0a746a3aa0405", + "outgoingViewKey": "7614a34227df4536d7f5168e8aad09cbdb76054f690e030c6acd8c7506caabd8", + "publicAddress": "f8c36799849623cea55c6eb362aaa1c91b61ab9688a996c5ac61a68e5f2946ec", + "createdAt": "2023-03-22T18:45:17.962Z" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "AD35193FA159CFD7440A3DFDFBE7ACB720CB4A44A441AB933071E2B9EF5DE90B", + "noteCommitment": { + "type": "Buffer", + "data": "base64:4ocGrsLmvh/+H7GyxtsHLom9hcxhjifMKHmPaNw1XmA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:gzH5qQblDGE8ivqOwYIddddRRkhP+RoqSVKAVmnjPhI=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1679510719091, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAkyOEkI2Wwdlfs2s8+d1H2U+9MGQMH5wpj/lEQtYEV3OvTQzaEuRnYe4OoekTupST7eJWytdWFw4W4QBEZSJkk9UNKHwNKn97iR0lKZuUGfuLRiH13iGSTfb72zM6OtOxIpdp8QVXZ65kFbbiFkWtwkVIGm/Vd8Aq/Yf4s/ZE2hoILMwagFJ849SV7bXSs0JBhaZ3CyWBZZ5aDovpkbNZuwNwjwUjip9MtA3ja+Xan9WZ8mBfS+iAFEp8GXa+l0YxVUKaNqSM9Sh3kSftk6cSf3FVYhDbQtWPsfYK4ON6DV+7PP7zILB1PU0MalHip8MN3yLhW9E1jCMKkE4bB8eACihQ2uel4tSKkr5fscQaSOb5qxcwnGPUmXuyyd8BhRcMf+NZpo6rqX3EVogqATNVCY+F+VoFLTsbf7OARk5Y5ruGWBnx3AyxmD6bqPzauSCZLkkbHC7T1mR0a3+guFPvC221CHOHlCGZzqm3cPJzo68PtM2okWrtkq0WrgCbSjsmUPICsHAV64H0nr8C2VAEHkkT18/dsb1My5A7JANyLhHFAvaclzk75WZbN79wostsy8rUCwN2tNKncN1vgaf8D4+SHEqVWFhsBuFaWk8tueZOl2YqKRNahUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwcQAdU15jx2fo+n2Jqr5rwuLqM623/FKQ3qdPFfXLjzh6+4bgDC9bv3H3SA3v6rZQIm3MV2twpuigfYijGFUqAQ==" + } + ] + }, + { + "type": "Buffer", + "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWKJERrP4mGh/y3Z0iMksm/2EcX3o90QQ5kgNL7CH5Z6Db1IwFCuv6PjP5CtNr33BTPOBXzye4lJpPf1sJw9EtHA0K3Qt2M9j4wx5Jw9tIoCkz+TA9RGwL/o12Mf0hD8U0loAd2gDsXO+/H9y4WYaZDTPNpPzqRWsyv/sdSQ/hGIVLR87whsiv9VI+jMAoqw8kCj1JUQ2gTy9/AUcnKGOlG3vHvkKSvbaZVlZ6vAQEZaiAuerLKMvVesT2QUuST0kP4h0YBXxNL5P0unhoTcgj6UCCGimYtbM2bPUvrNyR6At4v8LYzovURAzJXgPGf8jYR954V7Z5+haC68Ev4ja3+KHBq7C5r4f/h+xssbbBy6JvYXMYY4nzCh5j2jcNV5gBAAAAJFHnIoV0LlBbBNS/NQNuPUxJzqiJNP486ylVKp2xUI9JMZ8gRR5a1rAG0USHWdfoixhNQCFnYMv44cgD9ookAJfZ9NfhQ8L5ofDlKt1hSD5nLd6s307LkeButN4Iu49AYE6y7heTfgf1XldIxERSxR2pZziWKBJ99eoAgjOcKZXVk2TAjo31NjeWdJW5x4mpIHRjX799IrnZGutbHo/TxMPlVAcqX8CHyVYouQ+mnrbkWS+bM3ndin71jWWjCkGBAt2t97k1TYamSCzbGaai85sTjmyd/rvOmSHNIL18rfVrajfL4/bF/Leq42zWSFJAJGDBIyDgppjhXyd/Xh1s9w/woO5kZisqhc/XY8V7EvqMdW8R/PizW9CnE73CszUYi9TMyJPnJ7ZoZiyUnm/SKGcOSA6GAiYTssS8g6Tcmo2RdSxNHggdJ9AjpyRyqfXXY6q4oDjmabtSPPSeE/9yVc6/KkliNMqmH+4t4obwtWeRXIQvPtqTMh4bm42q5DcwnqcZyoOQET/n9KvKIDA0rql3wtc7cU0PJqP3MepkweGhyYBai5rqC/M6NhzU1wtwjb6cyvB/kTCWGWmAKq664gqaZ34DOXFMZVljrK7gQViPdWEVeVh91Myw1lu9BUzAn3QSNpdKGXGAFVGGNiRdzdICP0yzOnB35OKasJs/rNBTXZvGQFLB69xxVhB93ECeoJO1oA8V7jVkhyMfubW0odXUM1SIuC8/gTh8DmznNb9EuBzPguQRFb2OmXfmoiebRdgana/ApSTbSvVHtmD8G6v+JZQzZQg9JUdOhf3eu77nKjql7/oMzKYvuqDWIFvkCf3hzlowCwNGqjkokX5BwCOwYeFHRMZSEN7Kg/G+MzZ/7gug9Ktoj+BnE68MMH9pkoC7w06JsqO5ToDPrazLZgBFsqLW9+UKLSVT/jeAjOUM0ZeUez6swoBQkxIpyJQ8n4fODnVa5dM8dXnJ264HJKYKheAYud3I3zv2j1gnVXofnxz0JFdTXS320QULKVBsEJe9USEDwAz8Cs/yG3xqeyBOQ5OaG5qIct25W3HDFtg5NqukBqdyeNzfXxzwQApT4A5LWyzbS5HakomUjdUFf/55dOqR4nzSmP3QCZ5leEwpOq4qLzOh+QcoHLA8DlqSnoLyFMjuJE9DN5MlFC2/NYnnCrEzun6QJC6QX6pGmbu7tJbUps7CYKTg835JRJUM13xcAqkfMev8SY7C6+4UpzMxSo9gKuZB8yxCYkrxGOO39+DKh8L9rjEHsr70+BUqvU65u0voKGc1f+bo0zfj4ZcFsYRdn4kMmAOKc0Wm/MNrcUP/hIOiMmNPxLKY8mvCHj42thk36Fsj2HZ37gIJ3Z2kqzxzAayLz7LgFzpqKIa+uqGW99Y/g1wPXdAxn5xKN582/k0wJLSlT7wGhfpxnbNDO5SQh2OMv5yCL3gyRoDwKtEHNCbgWMmdcyMeckkjrPRZfkqKg0PPgiNaojQdEbsMrwGOMA76qbNb8Oum40TuuNcsTn2G3qiur0vQYve2C+9/kzjaoqhrG6CTC8CI9l3baoHX19fdw6MnDKeEnv8Lr703RuwGAwDiLpxFYXKAA==" + } ] } \ 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 3cfcb3d935..6a4fd1f47b 100644 --- a/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture +++ b/ironfish/src/wallet/__fixtures__/wallet.test.ts.fixture @@ -2566,60 +2566,6 @@ ] } ], - "Accounts addPendingTransaction should add transactions to accounts involved in the transaction": [ - { - "version": 1, - "id": "5cc59edf-b7a9-4c21-924d-26f25139435e", - "name": "a", - "spendingKey": "9d84f9febc7f51390657d359a21c44dff1c8cdb98a98e0db7db66e2db599bfe0", - "viewKey": "c42e05bf3a17642ca3aa3b73ff61e5f581b156ab8c516e11920738195a0c03115108848d9b86f468b619a88b70d8c2079c9f9d96c09e3a1c246840c619dd29b7", - "incomingViewKey": "c9af9ff44069d3836ad16232c29e3239dac052b6a15c60a641d7839ccebcb303", - "outgoingViewKey": "eb803b1de8d43b75f76a9f70a7bd3a4c479954b2bd006aee444b8604bde00774", - "publicAddress": "6f045687d7970c450f1e300c3c1aacc805a0b7209bb0cec2521f341f4188df38", - "createdAt": "2023-03-12T18:18:50.345Z" - }, - { - "version": 1, - "id": "6e9d60d0-4e2e-458b-ae4e-7927497dde26", - "name": "b", - "spendingKey": "47260f9fe78449144156c8cdcd453ae2a0958bf594bf61018e46884ba3e30e48", - "viewKey": "1b866ea28dc2866665e5b6895b448208e3a18ef52243db78ca0c59353196b4e44d04d7cca82b6378348050e42fd84b3a6885c73c84ec0e6c94c362ead7c2cc2f", - "incomingViewKey": "62d17f60ec581ec32837ee65fa136a4f18b34e5bf727cf826dbc4879c4666507", - "outgoingViewKey": "472b2a7778566e962790c16571f825e377d0029d71cb55aef259a00c82db35df", - "publicAddress": "87c757a6c79731497a5eb2e4b645bd26d8ef8cf13b3172373f162973d829585b", - "createdAt": "2023-03-12T18:18:50.354Z" - }, - { - "header": { - "sequence": 2, - "previousBlockHash": "AD35193FA159CFD7440A3DFDFBE7ACB720CB4A44A441AB933071E2B9EF5DE90B", - "noteCommitment": { - "type": "Buffer", - "data": "base64:2aRQUtlbSi7zeXBvWm4RE1Tnz5SoEHKssRV9c+TuzkQ=" - }, - "transactionCommitment": { - "type": "Buffer", - "data": "base64:yEH/NKoF1PohKWGfr8HK1oXs7/VCYcHK+dx2tvqYZss=" - }, - "target": "883423532389192164791648750371459257913741948437809479060803100646309888", - "randomness": "0", - "timestamp": 1678645131337, - "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", - "noteSize": 4, - "work": "0" - }, - "transactions": [ - { - "type": "Buffer", - "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAR/GHHSOhrdqZGxIJvYxF6bAkEoEq4uonj+wtixfqupmjGfGi+gcJThiFwbBO+3W7ffjFVtE1xWIM0vmWidbDATzuiUA54RhVX0bi6OL4EUuMCthHMm+YGSBtWTlif53OslAWA/XWNd3sOBUEyzQG3BE1d4fqvVbvNP4/dwn/1OcJTsuT3eEBRtFrUZ5zhqLDmSMqWeZVEbidTcK0rdtJeFfurGfV10KBnwA/a/gXVTOZamX+YR17aJFoj0trFnIZa1umKKbZWDUwd2vvhfpIYpuhS89yqH7gKixgK+4Xt2ssV1t+hoQeYPqkuje5vOauXMO2IEsn2mcvd57vRTo3zngBMXYXAqH8r1COSSQNNVVcWz/rfPV6Mzcr2midDGJEko4ICW35/mFrVGUIbxUCrJxtJrJDTNWiTGbQv5t+BJWeMh559v0yruAjm22BSLF/B4CuZPdGt/IbvRRsdI6/PqJzLC0bgVIdo4NGP1cIOguCiRMfx5P17teWKI3Lme0lQ5C4pD1C/LjjKa+7KJvbfz735jj9Qg05R08dc4yMb99wEawl6huc8qFw6/OWgAI/QCaeejpC/nMOQy3HQi/D0SlWPxqMtHVFNyPU4Mspf5D+sFHSHGG0oUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwD0zy9bYoAO0e9epiHLv5wHJdrPMPO5pqkxyb3/YO2VPj2JFPx+Y0KxixDDoJ4WeNwYKL9YrqQKYKlRyrp2ssDA==" - } - ] - }, - { - "type": "Buffer", - "data": "base64:AQEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvFl9TYSU0psFHNvOjc4dsbmpyFoweHpqa3LmxN9x4BaSfyygIKBL8v2GEUcOWv+g/BbdyS0mYl4EBOSbgNbsoTDQbnQDOKehVyzh3GcAfKKVbX0/h888fqFEeTGCe+4atGu4Iq6MAMFnYVLLk+4Akdh1aNA6Kw4Y/Mf3828udvMZqZWETl2jmM5oXZ+XDDtQ/vbWBB9y4JKhJ5nSVUv79oVLAmBJ9tlbAVFPqBNvxf6CTEJgoSGMQ1QO2//8exuxYmY6LZOT1PDdjE8pYDuU40nE0gav30AT+T/Hw/OtLaCLRRwp/ynbIskuRdGgjHLz5Fc/o4PiJagALselFSEzvdmkUFLZW0ou83lwb1puERNU58+UqBByrLEVfXPk7s5EBAAAAE2BY6UaVH3gMOBZqAlp44Eahuc2/DBRrBQbRqUisZhxKtU0GWhPLBfEroMPsSt7wlFdmkxF31jcWrvSKE4ORootKpQrdjgMf1St7jN4YS523QJA3jETpUQKbF3bopEdCZalvc8wt4RxefqL7m/TDMITseWejOQ+fIYRTFobMDNUGkbKZf4bf5fUgiGvN42SdaasyN71GcBX+snb6Lez89N/bnq8tmaMgUKugJsVwlF/T/kSgc2gHfjqLGSP0WKvkA+hG4XcpjLOUZPXtDwYg7hbP/P8OSm4GpegQbpi65tVIa9if+wui84hHUfU+IoNz6Wx+lIZ+aaIzyrnIUSNnH4sD/kAZDo4O/yb20X8c6Pqjq/kfdmboN5qkJeGFpRmzAVKyQVi1edk0rCi2msaFnYNzGm8GCltzyg2V/AHOxC7+Ba047a7fJNESIsyOGy8vNpEV8rMCwRrbMBhCm7CYAV7AZZV8zT3ISjbkxdYt3PempR18J9N/gjKmKtNjUiHp1rJ7JYpnWZGu66Uw2dmvDIs+/onR95zyv4/+FNk6kWxBqrzFl8WexX+8hY8LspIsiILVrw/mQo/tPzAHpgbrhGzibREzAUSn73PKeHg38t+DBlvvNFWC7uEl0qFPrOgPQnIUG/6HHpQJmqFaFfER7BOUaCf9SCR/SPRdueJ51YSQDibZKees14svBSke7ovcxGZ+ASYCpCzD8J3Da9Lg6sS8SwDOkiMafeGObYGGDa4jgc23ANMuMbJ76/bADSyUKkxziXXp7FgY5GT4qGe/guEMJfxTpElJU0Mhy8IEpd+Smq3Qg6ySRq41RcltJl+/PIsQCjgJB7TPVbp46TdJnrhScFVHCgurFtKze+V+ivQ5BZwyXY8KOKw6im02sdlSWHZBcGYRcVglfwRJ+cPPQUQGMGUDWruZtO1rTOyPGq0KtbNzFj8O+MJVeM1mJKmWZQdB2Cdat2lO0JCDh4pHtyPOMWl5ZtSvnXzGVBiQ5zu0ddHzACireSUekfDkSsAfwKYhQHr6k9qlRkrcsd1DT8lXGiXXqUWAl+I93tHyj2TdbsUrlq9fbd+uRy28Q2L1uVQTqW61nUMoFe7lc+bhKDw6nJQ/jEw6gpOajSCsMVV15GJyyoRwJbsM/qNX9Z2ZpKz7AYR9Ngz+udMttNIAvTOV54gvEQdIPRCbgfzBfWL4OuEmv0yx8s2Cp/Yzt6synh/9yYmyGmWsNMnFLxWF7OpIDiBTmeJbjLyetQU3uZ1tcYOuzNrRgRsG2HFUQPn5WS26Zo6LSLdzCePXY4wzYh6gf01dvSz/dz9CFjQkfbv8B7ejfw3TEGKvVOWQ95fZ6clZ9pBQQODxPKPkgIpWyM/imybQRQ2EeRqszmyeCO+WnX08r2rl4HEdhyzLJKstE82j15DeKwK/JNWNwABy37gf9Yp11h+DB4LcCksLHIaQ3EuP32DqxHDggpe42M4NW/qsh0Y9UkAw9UcW7PusLt/J3WMBVi5tBDUIGQtraAa/c4dPxSaWSHBwBGR5Fp75cdJBAUeAAhpcGSuq8RuJjLEa7r/OugbdMaJS4umqjvn6uzf0vElebTaAcX5mfKaBA==" - } - ], "Accounts addPendingTransaction should not decrypt notes for accounts that have already seen the transaction": [ { "version": 1, diff --git a/ironfish/src/wallet/account.test.ts b/ironfish/src/wallet/account.test.ts index 13e40e64ec..2d33701a78 100644 --- a/ironfish/src/wallet/account.test.ts +++ b/ironfish/src/wallet/account.test.ts @@ -452,6 +452,25 @@ describe('Accounts', () => { ) expect(unspentB).toHaveLength(0) }) + + it('should only save transactions to accounts involved in the transaction', async () => { + const { node } = await nodeTest.createSetup() + + const accountA = await useAccountFixture(node.wallet, 'a') + await useAccountFixture(node.wallet, 'b') + + const blockA1 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) + await expect(node.chain).toAddBlock(blockA1) + await node.wallet.updateHead() + + const saveSpy = jest.spyOn(accountA['walletDb'], 'saveTransaction') + + await useTxFixture(node.wallet, accountA, accountA) + + // tx added to accountA, but not accountB + expect(saveSpy).toHaveBeenCalledTimes(1) + expect(saveSpy.mock.lastCall?.[0]).toEqual(accountA) + }) }) describe('connectTransaction', () => { diff --git a/ironfish/src/wallet/account.ts b/ironfish/src/wallet/account.ts index b112ebfc8c..9299818cf5 100644 --- a/ironfish/src/wallet/account.ts +++ b/ironfish/src/wallet/account.ts @@ -193,6 +193,11 @@ export class Account { await this.walletDb.deleteUnspentNoteHash(this, spentNoteHash, spentNote, tx) } + // account did not receive or spend + if (assetBalanceDeltas.size === 0) { + return + } + transactionValue = { transaction, blockHash, @@ -507,6 +512,11 @@ export class Account { await this.walletDb.deleteUnspentNoteHash(this, spentNoteHash, spentNote, tx) } + // account did not receive or spend + if (assetBalanceDeltas.size === 0) { + return + } + const transactionValue = { transaction, blockHash: null, @@ -668,16 +678,6 @@ export class Account { return this.walletDb.hasPendingTransaction(this, hash, tx) } - async hasSpend(transaction: Transaction, tx?: IDatabaseTransaction): Promise { - for (const spend of transaction.spends) { - if ((await this.getNoteHash(spend.nullifier, tx)) !== undefined) { - return true - } - } - - return false - } - getTransactions(tx?: IDatabaseTransaction): AsyncGenerator> { return this.walletDb.loadTransactions(this, tx) } diff --git a/ironfish/src/wallet/wallet.test.ts b/ironfish/src/wallet/wallet.test.ts index 568a52b5e4..f2acaa3d62 100644 --- a/ironfish/src/wallet/wallet.test.ts +++ b/ironfish/src/wallet/wallet.test.ts @@ -1343,28 +1343,6 @@ describe('Accounts', () => { }) describe('addPendingTransaction', () => { - it('should add transactions to accounts involved in the transaction', async () => { - const { node } = await nodeTest.createSetup() - - const accountA = await useAccountFixture(node.wallet, 'a') - const accountB = await useAccountFixture(node.wallet, 'b') - - const blockA1 = await useMinerBlockFixture(node.chain, undefined, accountA, node.wallet) - await expect(node.chain).toAddBlock(blockA1) - await node.wallet.updateHead() - - const addSpyA = jest.spyOn(accountA, 'addPendingTransaction') - const addSpyB = jest.spyOn(accountB, 'addPendingTransaction') - - await useTxFixture(node.wallet, accountA, accountA) - - // tx added to accountA - expect(addSpyA).toHaveBeenCalledTimes(1) - - // tx not added to accountB - expect(addSpyB).toHaveBeenCalledTimes(0) - }) - it('should not decrypt notes for accounts that have already seen the transaction', async () => { const { node } = await nodeTest.createSetup() diff --git a/ironfish/src/wallet/wallet.ts b/ironfish/src/wallet/wallet.ts index e07cd74e42..331e848346 100644 --- a/ironfish/src/wallet/wallet.ts +++ b/ironfish/src/wallet/wallet.ts @@ -410,10 +410,6 @@ export class Wallet { const decryptedNotes = decryptedNotesByAccountId.get(account.id) ?? [] - if (decryptedNotes.length === 0 && !(await account.hasSpend(transaction))) { - continue - } - const transactionDeltas = await account.connectTransaction( blockHeader, transaction, @@ -528,10 +524,6 @@ export class Wallet { for (const account of accounts) { const decryptedNotes = decryptedNotesByAccountId.get(account.id) ?? [] - if (decryptedNotes.length === 0 && !(await account.hasSpend(transaction))) { - continue - } - await account.addPendingTransaction(transaction, decryptedNotes, this.chain.head.sequence) await this.upsertAssetsFromDecryptedNotes(account, decryptedNotes) } From 37c0e1c01f040f4cb7c73de5e2d062c2813dab5f Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Mon, 27 Mar 2023 08:24:47 -0700 Subject: [PATCH 13/21] fixes count of subscribed pool miners (#3696) the StratumServer has a property, 'subscribed', that it uses to count the number of subscribed miners. we don't decrement that counter when a subscribed miner is disconnected because of an error. this causes the miner count in status messages to hover at an incorrect level after miner errors. we also use the number of clients to indicate the number of miners when sending notifications for submitted blocks. this is not the right number since there may be connected clients that are only following the pool for status messages and aren't mining. --- ironfish/src/mining/pool.ts | 2 +- ironfish/src/mining/stratum/stratumServer.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ironfish/src/mining/pool.ts b/ironfish/src/mining/pool.ts index 8d36ecdb2c..6b6622b0f9 100644 --- a/ironfish/src/mining/pool.ts +++ b/ironfish/src/mining/pool.ts @@ -312,7 +312,7 @@ export class MiningPool { )}/s`, ) this.webhooks.map((w) => - w.poolSubmittedBlock(hashedHeaderHex, hashRate, this.stratum.clients.size), + w.poolSubmittedBlock(hashedHeaderHex, hashRate, this.stratum.subscribed), ) } else { this.logger.info(`Block was rejected: ${result.content.reason}`) diff --git a/ironfish/src/mining/stratum/stratumServer.ts b/ironfish/src/mining/stratum/stratumServer.ts index cf4f441b71..1ed842ba2a 100644 --- a/ironfish/src/mining/stratum/stratumServer.ts +++ b/ironfish/src/mining/stratum/stratumServer.ts @@ -325,6 +325,11 @@ export class StratumServer { client.socket.removeAllListeners() client.close() + + if (client.subscribed) { + this.subscribed-- + } + this.clients.delete(client.id) this.peers.removeConnectionCount(client) } From 0c7b044e39163368973dfa562b6e3f2188ca9ccb Mon Sep 17 00:00:00 2001 From: mat-if <97762857+mat-if@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:53:57 -0700 Subject: [PATCH 14/21] feat: genesisadd CLI command (#3678) * feat: genesisadd CLI command * modify the note selection so it is more specific this allows us to run the script multiple times if we need to, and won't try to double spend or anything also includes some minor code-review changes, for helptext and a flag --- ironfish-cli/src/commands/chain/genesisadd.ts | 203 ++++++++++++++++++ .../__fixtures__/genesis.test.slow.ts.fixture | 35 +++ ironfish/src/genesis/addGenesisTransaction.ts | 146 +++++++++++++ ironfish/src/genesis/genesis.test.slow.ts | 182 +++++++++++++++- ironfish/src/genesis/index.ts | 1 + 5 files changed, 560 insertions(+), 7 deletions(-) create mode 100644 ironfish-cli/src/commands/chain/genesisadd.ts create mode 100644 ironfish/src/genesis/addGenesisTransaction.ts diff --git a/ironfish-cli/src/commands/chain/genesisadd.ts b/ironfish-cli/src/commands/chain/genesisadd.ts new file mode 100644 index 0000000000..477fae6281 --- /dev/null +++ b/ironfish-cli/src/commands/chain/genesisadd.ts @@ -0,0 +1,203 @@ +/* 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 { MEMO_LENGTH } from '@ironfish/rust-nodejs' +import { + addGenesisTransaction, + BlockSerde, + CurrencyUtils, + GenesisBlockAllocation, + IJSON, + isValidPublicAddress, +} from '@ironfish/sdk' +import { CliUx, Flags } from '@oclif/core' +import fs from 'fs/promises' +import { IronfishCommand } from '../../command' +import { LocalFlags } from '../../flags' + +export default class GenesisAddCommand extends IronfishCommand { + static hidden = true + + static flags = { + ...LocalFlags, + account: Flags.string({ + char: 'a', + required: true, + description: 'The name of the account to reallocate from', + }), + allocations: Flags.string({ + required: true, + description: + 'A CSV file with the format address,amountInIron,memo containing genesis block allocations', + }), + totalAmount: Flags.string({ + char: 'g', + required: true, + description: 'The total prior allocation to the given account', + }), + dry: Flags.boolean({ + default: false, + description: 'Display genesis block allocations without creating the genesis block', + }), + } + + async start(): Promise { + const { flags } = await this.parse(GenesisAddCommand) + + const node = await this.sdk.node() + await node.openDB() + + const account = node.wallet.getAccountByName(flags.account) + if (account === null) { + this.log(`Account ${flags.account} does not exist, make sure it is imported first`) + this.exit(0) + return + } + + const totalAmount = CurrencyUtils.decodeIron(flags.totalAmount) + const csv = await fs.readFile(flags.allocations, 'utf-8') + const result = parseAllocationsFile(csv) + + if (!result.ok) { + this.error(result.error) + } + + const totalSupply: bigint = result.allocations.reduce((prev, cur) => { + return prev + cur.amountInOre + }, 0n) + + if (totalSupply !== totalAmount) { + this.error( + `Allocations file contains ${CurrencyUtils.encodeIron( + totalSupply, + )} $IRON, but --totalAmount expects ${flags.totalAmount} $IRON.`, + ) + } + + const allocations: GenesisBlockAllocation[] = result.allocations + + // Log genesis block info + this.log(`Genesis block will be modified with the following values in a new transaction:`) + this.log(`Allocations:`) + const columns: CliUx.Table.table.Columns = { + identity: { + header: 'ADDRESS', + get: (row: GenesisBlockAllocation) => row.publicAddress, + }, + amount: { + header: 'AMOUNT ($IRON)', + get: (row: GenesisBlockAllocation) => { + return CurrencyUtils.encodeIron(row.amountInOre) + }, + }, + memo: { + header: 'MEMO', + get: (row: GenesisBlockAllocation) => row.memo, + }, + } + + CliUx.ux.table(allocations, columns, { + printLine: (line) => this.log(line), + }) + + // Display duplicates if they exist + const duplicates = getDuplicates(allocations) + if (duplicates.length > 0) { + this.log( + `\n/!\\ Allocations contains the following duplicate addresses. This will not cause errors, but may be a mistake. /!\\`, + ) + for (const duplicate of duplicates) { + this.log(duplicate) + } + this.log('\n') + } + + // Exit if dry run, otherwise confirm + if (flags.dry) { + this.exit(0) + } else { + const result = await CliUx.ux.confirm('\nCreate new genesis block? (y)es / (n)o') + if (!result) { + this.exit(0) + } + } + + this.log('\nBuilding a genesis block...') + const { block } = await addGenesisTransaction(node, account, allocations, this.logger) + + this.log(`\nGenesis Block`) + const serialized = BlockSerde.serialize(block) + this.log(IJSON.stringify(serialized, ' ')) + } +} + +const getDuplicates = (allocations: readonly GenesisBlockAllocation[]): string[] => { + const duplicateSet = new Set() + const nonDuplicateSet = new Set() + + for (const alloc of allocations) { + if (nonDuplicateSet.has(alloc.publicAddress)) { + duplicateSet.add(alloc.publicAddress) + } else { + nonDuplicateSet.add(alloc.publicAddress) + } + } + + return [...duplicateSet] +} + +const parseAllocationsFile = ( + fileContent: string, +): { ok: true; allocations: GenesisBlockAllocation[] } | { ok: false; error: string } => { + const allocations: GenesisBlockAllocation[] = [] + + let lineNum = 0 + for (const line of fileContent.split(/[\r\n]+/)) { + lineNum++ + if (line.trim().length === 0) { + continue + } + + const [address, amountInIron, memo, ...rest] = line.split(',').map((v) => v.trim()) + + if (rest.length > 0) { + return { + ok: false, + error: `Line ${lineNum}: (${line}) contains more than 3 values.`, + } + } + + // Check address length + if (!isValidPublicAddress(address)) { + return { + ok: false, + error: `Line ${lineNum}: (${line}) has an invalid public address.`, + } + } + + // Check amount is positive and decodes as $IRON + const amountInOre = CurrencyUtils.decodeIron(amountInIron) + if (amountInOre < 0) { + return { + ok: false, + error: `Line ${lineNum}: (${line}) contains a negative $IRON amount.`, + } + } + + // Check memo length + if (Buffer.from(memo).byteLength > MEMO_LENGTH) { + return { + ok: false, + error: `Line ${lineNum}: (${line}) contains a memo with byte length > ${MEMO_LENGTH}.`, + } + } + + allocations.push({ + publicAddress: address, + amountInOre: amountInOre, + memo: memo, + }) + } + + return { ok: true, allocations } +} diff --git a/ironfish/src/genesis/__fixtures__/genesis.test.slow.ts.fixture b/ironfish/src/genesis/__fixtures__/genesis.test.slow.ts.fixture index 1b89c652b4..7ec0c56675 100644 --- a/ironfish/src/genesis/__fixtures__/genesis.test.slow.ts.fixture +++ b/ironfish/src/genesis/__fixtures__/genesis.test.slow.ts.fixture @@ -11,5 +11,40 @@ "publicAddress": "3aade08766f374726252d244e44836524d4f89b0c46c56dcb5d3da8eedc0ac33", "createdAt": "2023-03-12T18:21:15.400Z" } + ], + "addGenesisTransaction Can create a new genesis block with an added transaction": [ + { + "version": 1, + "id": "4649f9ad-b454-49d7-946b-fc4b624e2457", + "name": "account1", + "spendingKey": "d4aada789ad4ee23fd610feef896a14bab2adb27cdfb81ad862a6c5479f6137e", + "viewKey": "341e035eb513031b7ee64c16844b163c0079e61f720e02ef50f7761e65866ad17336dc7c7a21cd59092c41a7aeb5a614fcbd9d16d28cf6c56b02daa125cc2336", + "incomingViewKey": "fe14049643a4f7a6df042b1d4612b27cdbdc46432d62d12e2c2655fa768a1001", + "outgoingViewKey": "35afb0f100570a51ab6f2497a6d3e836d5a570520edb27cd93025ed3b492ce6f", + "publicAddress": "6e00ecabbf8f50570b3462822d2f817e8bfe1d681ec5c2706cd39efdb9f3bb87", + "createdAt": "2023-03-21T21:39:39.238Z" + }, + { + "version": 1, + "id": "e55d269f-d79c-49db-84db-d1e5bae5b309", + "name": "account2", + "spendingKey": "fd210152449db354ed0fbe7ea915041728790185e89a0be2e428d453ac16245f", + "viewKey": "dbf261d0b5c1941050911d801ffba3e81877ebcd2d20aafccf2612a681eaa564dbb63e7b7aa552eca5539799811fdbc35e00b0507d34ea196b45ca2b2b1f0e9e", + "incomingViewKey": "3d46dbe6482a9e494c6187a7ba41d8b7aaace8e47e77f7c086e16e1f87c8f201", + "outgoingViewKey": "6d1423a452ffa1d10c220c92e493d368ac55f8e6317954602b74ea59a7a049d2", + "publicAddress": "82af4ab5a7c4889804ee685cc1cb0669e8a08ca480fe338a5f8d5bb26a114c5b", + "createdAt": "2023-03-21T21:39:39.239Z" + }, + { + "version": 1, + "id": "e6669000-72b0-4954-875d-6868375701ed", + "name": "account3", + "spendingKey": "f2eed26802b20071715b6a2b113c7df2ea2552cf1e45c65cfe7c2375656e397b", + "viewKey": "ea00a65b1e2bb1e53a9f92e1d13a264132ef4c791f6b0d42c8778538748b15048a9af9264eabcf4fbb470ce7ad2ac8955aeba1f72131df8058f31c6b4c99679c", + "incomingViewKey": "13bb18a8b53d8cfcc1cd69b00afcadc227a00599cd8081f4d8bd32e684e19900", + "outgoingViewKey": "b206c06a5b26c3ceae1c2189835c68cb7da06adf177f414a09d28e1b525bb5c8", + "publicAddress": "3233dd96ca906d54f76625f215ab43c1202345493dce2d85d48f71a2a2304599", + "createdAt": "2023-03-21T21:39:39.239Z" + } ] } \ No newline at end of file diff --git a/ironfish/src/genesis/addGenesisTransaction.ts b/ironfish/src/genesis/addGenesisTransaction.ts new file mode 100644 index 0000000000..555bb25904 --- /dev/null +++ b/ironfish/src/genesis/addGenesisTransaction.ts @@ -0,0 +1,146 @@ +/* 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, + Note as NativeNote, + Transaction as NativeTransaction, +} from '@ironfish/rust-nodejs' +import { Logger } from '../logger' +import { IronfishNode } from '../node' +import { Block, BlockHeader } from '../primitives' +import { transactionCommitment } from '../primitives/blockheader' +import { Transaction } from '../primitives/transaction' +import { CurrencyUtils } from '../utils' +import { Account } from '../wallet' +import { GenesisBlockAllocation } from './makeGenesisBlock' + +export async function addGenesisTransaction( + node: IronfishNode, + account: Account, + allocations: GenesisBlockAllocation[], + logger: Logger, +): Promise<{ block: Block }> { + logger = logger.withTag('addGenesisTransaction') + + if (!account.spendingKey) { + throw new Error('Must be a full account, not a view account') + } + + // Sum the allocations to get the total number of coins + const allocationSum = allocations.reduce((sum, cur) => sum + cur.amountInOre, 0n) + const allocationSumInIron = CurrencyUtils.encodeIron(allocationSum) + + logger.info('Generating a transaction for distributing allocations...') + + // Get a previous note owned by the given account from the existing genesis block + let note: NativeNote | null = null + let witness = null + const genesisTransactions = await node.chain.getBlockTransactions(node.chain.genesis) + for (const { transaction, initialNoteIndex } of genesisTransactions) { + let noteIndex = -1 + for (const encryptedNote of transaction.notes) { + noteIndex += 1 + // If this account can't decrypt this note, we can't use it + const decryptedNote = encryptedNote.decryptNoteForOwner(account.incomingViewKey) + if (decryptedNote == null) { + continue + } + + // If the nullifier has already been revealed, we can't use it + const nullifier = decryptedNote.nullifier( + account.viewKey, + BigInt(initialNoteIndex + noteIndex), + ) + if (await node.chain.nullifiers.contains(nullifier)) { + continue + } + + // We want the note with the exact value + if (decryptedNote.value() !== allocationSum) { + continue + } + + witness = await node.chain.notes.witness(initialNoteIndex + noteIndex) + note = decryptedNote.takeReference() + decryptedNote.returnReference() + break + } + + if (note != null) { + break + } + } + + if (note == null) { + throw new Error( + 'The given account does not have a suitable note to spend for the new allocations', + ) + } + + if (witness == null) { + throw new Error('The witness is missing, this should not happen') + } + + if (note.value() !== allocationSum) { + throw new Error('The value of the note to spend does not match the sum of the allocations') + } + + // Create the new transaction to be appended to the new genesis block + const transaction = new NativeTransaction(account.spendingKey) + logger.info(` Generating a spend for ${allocationSumInIron} coins...`) + transaction.spend(note, witness) + + for (const alloc of allocations) { + logger.info( + ` Generating an output for ${CurrencyUtils.encodeIron(alloc.amountInOre)} coins for ${ + alloc.publicAddress + }...`, + ) + const note = new NativeNote( + alloc.publicAddress, + BigInt(alloc.amountInOre), + alloc.memo, + Asset.nativeId(), + account.publicAddress, + ) + transaction.output(note) + } + + logger.info(' Posting the transaction...') + const postedTransaction = new Transaction(transaction.post(undefined, BigInt(0))) + + logger.info('Creating the modified genesis block...') + // Get the existing genesis block + const genesisBlock = await node.chain.getBlock(node.chain.genesis) + if (genesisBlock == null) { + throw new Error('An existing genesis block was not found') + } + + // Append the new transaction + genesisBlock.transactions.push(postedTransaction) + + // Add the new notes to the merkle tree + await node.chain.notes.addBatch(postedTransaction.notes) + + // Generate a new block header for the new genesis block + const noteCommitment = await node.chain.notes.rootHash() + const noteSize = await node.chain.notes.size() + const newGenesisHeader = new BlockHeader( + 1, + genesisBlock.header.previousBlockHash, + noteCommitment, + transactionCommitment(genesisBlock.transactions), + genesisBlock.header.target, + genesisBlock.header.randomness, + genesisBlock.header.timestamp, + genesisBlock.header.graffiti, + noteSize, + ) + + genesisBlock.header = newGenesisHeader + + logger.info('Block complete.') + return { block: genesisBlock } +} diff --git a/ironfish/src/genesis/genesis.test.slow.ts b/ironfish/src/genesis/genesis.test.slow.ts index 9e51514271..1c72d272ce 100644 --- a/ironfish/src/genesis/genesis.test.slow.ts +++ b/ironfish/src/genesis/genesis.test.slow.ts @@ -7,6 +7,7 @@ import { Target } from '../primitives/target' import { IJSON } from '../serde' import { createNodeTest, useAccountFixture } from '../testUtilities' import { acceptsAllTarget } from '../testUtilities/helpers/blockchain' +import { addGenesisTransaction } from './addGenesisTransaction' import { GenesisBlockInfo, makeGenesisBlock } from './makeGenesisBlock' describe('Read genesis block', () => { @@ -59,8 +60,7 @@ describe('Create genesis block', () => { const node = nodeTest.node const chain = nodeTest.chain - const amountNumber = 5n - const amountBigint = BigInt(amountNumber) + const amount = 5n // Construct parameters for the genesis block const account = await useAccountFixture(node.wallet, 'test') @@ -69,7 +69,7 @@ describe('Create genesis block', () => { target: Target.maxTarget(), allocations: [ { - amountInOre: amountNumber, + amountInOre: amount, publicAddress: account.publicAddress, memo: 'test', }, @@ -98,8 +98,8 @@ describe('Create genesis block', () => { // Check that the balance is what's expected await expect(node.wallet.getBalance(account, Asset.nativeId())).resolves.toMatchObject({ - confirmed: amountBigint, - unconfirmed: amountBigint, + confirmed: amount, + unconfirmed: amount, }) // Ensure we can construct blocks after that block @@ -136,8 +136,8 @@ describe('Create genesis block', () => { await expect( newNode.wallet.getBalance(accountNewNode, Asset.nativeId()), ).resolves.toMatchObject({ - confirmed: amountBigint, - unconfirmed: amountBigint, + confirmed: amount, + unconfirmed: amount, }) // Ensure we can construct blocks after that block @@ -150,3 +150,171 @@ describe('Create genesis block', () => { expect(newBlock).toBeTruthy() }) }) + +describe('addGenesisTransaction', () => { + const nodeTest = createNodeTest(false, { autoSeed: false }) + let targetMeetsSpy: jest.SpyInstance + let targetSpy: jest.SpyInstance + + beforeAll(() => { + targetMeetsSpy = jest.spyOn(Target, 'meets').mockImplementation(() => true) + targetSpy = jest.spyOn(Target, 'calculateTarget').mockImplementation(acceptsAllTarget) + }) + + afterAll(() => { + targetMeetsSpy.mockClear() + targetSpy.mockClear() + }) + + it('Can create a new genesis block with an added transaction', async () => { + // Initialize the database and chain + const originalNode = nodeTest.node + const originalChain = nodeTest.chain + + // Construct parameters for the genesis block + const account1Original = await useAccountFixture(originalNode.wallet, 'account1') + const account2Original = await useAccountFixture(originalNode.wallet, 'account2') + const account3Original = await useAccountFixture(originalNode.wallet, 'account3') + + const info: GenesisBlockInfo = { + timestamp: Date.now(), + target: Target.maxTarget(), + allocations: [ + { + amountInOre: 100n, + publicAddress: account1Original.publicAddress, + memo: 'account1', + }, + { + amountInOre: 100n, + publicAddress: account2Original.publicAddress, + memo: 'account2', + }, + ], + } + + // Build the original genesis block itself + const { block: originalBlock } = await makeGenesisBlock( + originalChain, + info, + originalNode.logger, + ) + + // Add the block to the chain + const originalAddBlock = await originalChain.addBlock(originalBlock) + expect(originalAddBlock.isAdded).toBeTruthy() + + const newAllocations = [ + { + amountInOre: 50n, + publicAddress: account1Original.publicAddress, + memo: 'account1', + }, + { + amountInOre: 25n, + publicAddress: account2Original.publicAddress, + memo: 'account2', + }, + { + amountInOre: 25n, + publicAddress: account3Original.publicAddress, + memo: 'account3', + }, + ] + + // Account 1: 100 in original allocation, but 50 used for the 2nd allocation + const account1Amount = 50n + // Account 2: 100 in the original allocation, and 25 in the 2nd allocation + const account2Amount = 125n + // Account 3: 25 in the 2nd allocation + const account3Amount = 25n + + // Build the modified genesis block + const { block } = await addGenesisTransaction( + originalNode, + account1Original, + newAllocations, + originalNode.logger, + ) + + // Compare the original parameters with the new one + expect(originalBlock.header.sequence).toEqual(block.header.sequence) + expect(originalBlock.header.previousBlockHash).toEqual(block.header.previousBlockHash) + expect(originalBlock.header.target).toEqual(block.header.target) + expect(originalBlock.header.randomness).toEqual(block.header.randomness) + expect(originalBlock.header.timestamp).toEqual(block.header.timestamp) + expect(originalBlock.header.graffiti).toEqual(block.header.graffiti) + expect(originalBlock.header.noteCommitment).not.toEqual(block.header.noteCommitment) + expect(originalBlock.header.noteSize).not.toEqual(block.header.noteSize) + expect(originalBlock.header.transactionCommitment).not.toEqual( + block.header.transactionCommitment, + ) + expect(originalBlock.transactions.length).not.toEqual(block.transactions.length) + + // Balance should still be zero, since generating the block should clear out + // any notes made in the process + await expect( + originalNode.wallet.getBalance(account1Original, Asset.nativeId()), + ).resolves.toMatchObject({ + confirmed: BigInt(0), + unconfirmed: BigInt(0), + }) + + // Create a new node + const { strategy, chain, node } = await nodeTest.createSetup() + + // Import accounts + const account1 = await node.wallet.importAccount(account1Original) + const account2 = await node.wallet.importAccount(account2Original) + const account3 = await node.wallet.importAccount(account3Original) + + // Next, serialize it in the same way that the genesis command serializes it + const serialized = BlockSerde.serialize(block) + const jsonedBlock = IJSON.stringify(serialized, ' ') + + // Deserialize the block and add it to the new chain + const result = IJSON.parse(jsonedBlock) as SerializedBlock + const deserializedBlock = BlockSerde.deserialize(result) + const addedBlock = await chain.addBlock(deserializedBlock) + expect(addedBlock.isAdded).toBe(true) + + await node.wallet.updateHead() + + // Check that the balance is what's expected + await expect(node.wallet.getBalance(account1, Asset.nativeId())).resolves.toMatchObject({ + confirmed: account1Amount, + unconfirmed: account1Amount, + }) + await expect(node.wallet.getBalance(account2, Asset.nativeId())).resolves.toMatchObject({ + confirmed: account2Amount, + unconfirmed: account2Amount, + }) + await expect(node.wallet.getBalance(account3, Asset.nativeId())).resolves.toMatchObject({ + confirmed: account3Amount, + unconfirmed: account3Amount, + }) + + // Ensure we can construct blocks after that block + const minersfee = await strategy.createMinersFee( + BigInt(0), + block.header.sequence + 1, + generateKey().spendingKey, + ) + const additionalBlock = await chain.newBlock([], minersfee) + expect(additionalBlock).toBeTruthy() + + // Validate parameters again to make sure they're what's expected + expect(deserializedBlock.header.sequence).toEqual(block.header.sequence) + expect(deserializedBlock.header.previousBlockHash).toEqual(block.header.previousBlockHash) + expect(deserializedBlock.header.target).toEqual(block.header.target) + expect(deserializedBlock.header.randomness).toEqual(block.header.randomness) + expect(deserializedBlock.header.timestamp).toEqual(block.header.timestamp) + expect(deserializedBlock.header.graffiti).toEqual(block.header.graffiti) + expect(deserializedBlock.header.noteCommitment).toEqual(block.header.noteCommitment) + expect(deserializedBlock.header.noteSize).toEqual(block.header.noteSize) + expect(deserializedBlock.header.transactionCommitment).toEqual( + block.header.transactionCommitment, + ) + expect(deserializedBlock.transactions.length).toEqual(block.transactions.length) + }) +}) diff --git a/ironfish/src/genesis/index.ts b/ironfish/src/genesis/index.ts index 354339927e..1268e948a9 100644 --- a/ironfish/src/genesis/index.ts +++ b/ironfish/src/genesis/index.ts @@ -1,4 +1,5 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +export * from './addGenesisTransaction' export * from './makeGenesisBlock' From a90584c7232c853419e0821bfc8498f86a2a9a13 Mon Sep 17 00:00:00 2001 From: Jason Spafford Date: Mon, 27 Mar 2023 17:34:00 -0400 Subject: [PATCH 15/21] Missing use http flag in start (#3684) You cannot start the HTTP adapter like other adapters because this is missing. --- ironfish-cli/src/commands/start.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ironfish-cli/src/commands/start.ts b/ironfish-cli/src/commands/start.ts index 6476ebfb5a..ad5a8f72a5 100644 --- a/ironfish-cli/src/commands/start.ts +++ b/ironfish-cli/src/commands/start.ts @@ -22,6 +22,8 @@ import { RpcTcpPortFlagKey, RpcTcpTlsFlag, RpcTcpTlsFlagKey, + RpcUseHttpFlag, + RpcUseHttpFlagKey, RpcUseIpcFlag, RpcUseIpcFlagKey, RpcUseTcpFlag, @@ -43,6 +45,7 @@ export default class Start extends IronfishCommand { [DataDirFlagKey]: DataDirFlag, [RpcUseIpcFlagKey]: { ...RpcUseIpcFlag, allowNo: true }, [RpcUseTcpFlagKey]: { ...RpcUseTcpFlag, allowNo: true }, + [RpcUseHttpFlagKey]: { ...RpcUseHttpFlag, allowNo: true }, [RpcTcpTlsFlagKey]: RpcTcpTlsFlag, [RpcTcpHostFlagKey]: RpcTcpHostFlag, [RpcTcpPortFlagKey]: RpcTcpPortFlag, From bce6b5de5aa2c3403fc39fe683723f2c926a0ee1 Mon Sep 17 00:00:00 2001 From: Daniel Cogan Date: Mon, 27 Mar 2023 17:44:02 -0700 Subject: [PATCH 16/21] Combine docker deploy workflows (#3701) --- .github/workflows/deploy-node-aws.yml | 34 ----- .../workflows/deploy-node-docker-image.yml | 120 ++++++++++++++++++ .github/workflows/deploy-node-github-beta.yml | 32 ----- .github/workflows/deploy-node-github.yml | 32 ----- ironfish-cli/scripts/deploy-docker.sh | 29 ----- 5 files changed, 120 insertions(+), 127 deletions(-) delete mode 100644 .github/workflows/deploy-node-aws.yml create mode 100644 .github/workflows/deploy-node-docker-image.yml delete mode 100644 .github/workflows/deploy-node-github-beta.yml delete mode 100644 .github/workflows/deploy-node-github.yml delete mode 100755 ironfish-cli/scripts/deploy-docker.sh diff --git a/.github/workflows/deploy-node-aws.yml b/.github/workflows/deploy-node-aws.yml deleted file mode 100644 index 60048fe360..0000000000 --- a/.github/workflows/deploy-node-aws.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Deploy Node Image to AWS -on: workflow_dispatch - -jobs: - Deploy: - name: Deploy - runs-on: ubuntu-latest - - steps: - - name: Check out Git repository - uses: actions/checkout@v3 - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Login to AWS Registry - run: aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $AWS_REGISTRY_URL - env: - AWS_REGISTRY_URL: ${{ secrets.AWS_NODE_REGISTRY_URL }} - - - name: Build Node Image - run: ./ironfish-cli/scripts/build-docker.sh - - - name: Deploy Node Image - run: ./ironfish-cli/scripts/deploy-docker.sh - env: - REGISTRY_URL: ${{ secrets.AWS_NODE_REGISTRY_URL }} - PACKAGE_NAME: ironfish - GITHUB_REF: ${GITHUB_REF_NAME} - GITHUB_SHA: ${GITHUB_SHA} diff --git a/.github/workflows/deploy-node-docker-image.yml b/.github/workflows/deploy-node-docker-image.yml new file mode 100644 index 0000000000..f6001d6473 --- /dev/null +++ b/.github/workflows/deploy-node-docker-image.yml @@ -0,0 +1,120 @@ +name: Deploy Node Docker Image +on: + workflow_dispatch: + inputs: + github_tag_mainnet: + description: 'GitHub:mainnet' + type: boolean + default: false + github_tag_testnet: + description: 'GitHub:testnet' + type: boolean + default: false + aws_tag_mainnet: + description: 'AWS:mainnet' + type: boolean + default: false + aws_tag_testnet: + description: 'AWS:testnet' + type: boolean + default: false + aws_tag_git_sha: + description: 'AWS:{GIT_SHA}' + type: boolean + default: false + +permissions: + contents: read + packages: write + +jobs: + Deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Login to GitHub Registry + run: echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_USER} --password-stdin ghcr.io + env: + GITHUB_USER: ${{ secrets.BREW_GITHUB_USERNAME }} + GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to AWS Registry + run: aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $AWS_REGISTRY_URL + env: + AWS_REGISTRY_URL: ${{ secrets.AWS_NODE_REGISTRY_URL }} + + - name: Build Node Image + run: ./ironfish-cli/scripts/build-docker.sh + + - name: Deploy Node Image to GitHub:mainnet + if: ${{ inputs.github_tag_mainnet }} + run: | + docker tag ironfish ghcr.io/iron-fish/ironfish:mainnet + docker push ghcr.io/iron-fish/ironfish:mainnet + + # If we are deploying a new public release to mainnet + # also update the docker registry :latest tag for hygiene + - name: Deploy Node Image to GitHub:latest + if: ${{ inputs.github_tag_mainnet }} + run: | + docker tag ironfish ghcr.io/iron-fish/ironfish:latest + docker push ghcr.io/iron-fish/ironfish:latest + + # Used if we are deploying a new version (e.g. v1.1) + # This is only executed when deploying a new release to mainnet + - name: Deploy Node Image to GitHub:${{ github.ref_name }} + if: ${{ inputs.github_tag_mainnet && github.event.ref_type == 'tag'}} + run: | + docker tag ironfish ghcr.io/iron-fish/ironfish:${{ github.ref_name }} + docker push ghcr.io/iron-fish/ironfish:${{ github.ref_name }} + + - name: Deploy Node Image to GitHub:testnet + if: ${{ inputs.github_tag_testnet }} + run: | + docker tag ironfish ghcr.io/iron-fish/ironfish:testnet + docker push ghcr.io/iron-fish/ironfish:testnet + + - name: Deploy Node Image to AWS:mainnet + if: ${{ inputs.aws_tag_mainnet }} + run: | + docker tag ironfish ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:mainnet + docker push ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:mainnet + + # If we are deploying a new public release to mainnet + # also update the docker registry :latest tag for hygiene + - name: Deploy Node Image to AWS:latest + if: ${{ inputs.aws_tag_mainnet }} + run: | + docker tag ironfish ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:latest + docker push ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:latest + + # Used if we are deploying a new version (e.g. v1.1) + # This is only executed when deploying a new release to mainnet + - name: Deploy Node Image to AWS:${{ github.ref_name }} + if: ${{ inputs.aws_tag_mainnet && github.event.ref_type == 'tag'}} + run: | + docker tag ironfish ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:${{ github.ref_name }} + docker push ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:${{ github.ref_name }} + + - name: Deploy Node Image to AWS:testnet + if: ${{ inputs.aws_tag_testnet }} + run: | + docker tag ironfish ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:testnet + docker push ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:testnet + + - name: Deploy Node Image to AWS:${{ github.sha }} + if: ${{ inputs.aws_tag_git_sha }} + run: | + docker tag ironfish ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:${{ github.sha }} + docker push ${{ secrets.AWS_NODE_REGISTRY_URL }}/ironfish:${{ github.sha }} diff --git a/.github/workflows/deploy-node-github-beta.yml b/.github/workflows/deploy-node-github-beta.yml deleted file mode 100644 index d59f7dfb1e..0000000000 --- a/.github/workflows/deploy-node-github-beta.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Deploy Node Image to Github Beta -on: workflow_dispatch - -permissions: - contents: read - packages: write - -jobs: - Deploy: - name: Deploy - runs-on: ubuntu-latest - - steps: - - name: Check out Git repository - uses: actions/checkout@v3 - - - name: Login to Github Registry - run: echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_USER} --password-stdin ghcr.io - env: - GITHUB_USER: ${{ secrets.BREW_GITHUB_USERNAME }} - GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} - - - name: Build Node Image - run: ./ironfish-cli/scripts/build-docker.sh - - - name: Deploy Node Image to Github - run: ./ironfish-cli/scripts/deploy-docker.sh - env: - REGISTRY_URL: ghcr.io/iron-fish - PACKAGE_NAME: ironfish-beta - GITHUB_REF: ${GITHUB_REF_NAME} - GITHUB_SHA: "$GITHUB_SHA" diff --git a/.github/workflows/deploy-node-github.yml b/.github/workflows/deploy-node-github.yml deleted file mode 100644 index 9a7534fc89..0000000000 --- a/.github/workflows/deploy-node-github.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Deploy Node Image to Github -on: workflow_dispatch - -permissions: - contents: read - packages: write - -jobs: - Deploy: - name: Deploy - runs-on: ubuntu-latest - - steps: - - name: Check out Git repository - uses: actions/checkout@v3 - - - name: Login to Github Registry - run: echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_USER} --password-stdin ghcr.io - env: - GITHUB_USER: ${{ secrets.BREW_GITHUB_USERNAME }} - GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} - - - name: Build Node Image - run: ./ironfish-cli/scripts/build-docker.sh - - - name: Deploy Node Image to Github - run: ./ironfish-cli/scripts/deploy-docker.sh - env: - REGISTRY_URL: ghcr.io/iron-fish - PACKAGE_NAME: ironfish - GITHUB_REF: ${GITHUB_REF_NAME} - GITHUB_SHA: ${GITHUB_SHA} diff --git a/ironfish-cli/scripts/deploy-docker.sh b/ironfish-cli/scripts/deploy-docker.sh deleted file mode 100755 index 3cea2277e6..0000000000 --- a/ironfish-cli/scripts/deploy-docker.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -cd "$(dirname "$0")" - -if [ -z "${REGISTRY_URL-}" ]; then - echo "Set REGISTRY_URL before running deploy-docker.sh" - exit 1 -fi - -if [ -z "${PACKAGE_NAME-}" ]; then - echo "Set PACKAGE_NAME before running deploy-docker.sh" - exit 1 -fi - -if [ -z "${GITHUB_SHA-}" ]; then - echo "Set GITHUB_SHA before running deploy-docker.sh" - exit 1 -fi - -if [ -z "${GITHUB_REF-}" ]; then - echo "Set GITHUB_REF before running deploy-docker.sh" - exit 1 -fi - -docker tag ironfish:latest ${REGISTRY_URL}/${PACKAGE_NAME}:latest -docker tag ironfish:latest ${REGISTRY_URL}/${PACKAGE_NAME}:${GITHUB_REF} -docker tag ironfish:latest ${REGISTRY_URL}/${PACKAGE_NAME}:${GITHUB_SHA} - -docker push --all-tags ${REGISTRY_URL}/${PACKAGE_NAME} From 9cb58beac60c38048431e7ba809e54addf9af03c Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 28 Mar 2023 10:09:10 -0700 Subject: [PATCH 17/21] uses default node account in pool by default (#3697) by default the name of the account that a mining pool uses for payout transactions is hardcoded to 'default'. this poses two potential problems: 1. if the default account on the pool node is not 'default', then 'default' may not have any funds for payouts 2. 'default' may not exist on the pool node instead of hardcoding the default value of the pool account name we can leave it unset by default. at the time of a payout the pool can then use the default account of its node. --- ironfish/src/fileStores/config.ts | 6 ++-- ironfish/src/mining/poolShares.test.ts | 43 ++++++++++++++++++++++++++ ironfish/src/mining/poolShares.ts | 18 +++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/ironfish/src/fileStores/config.ts b/ironfish/src/fileStores/config.ts index 6594c2b5f4..8fa29a6683 100644 --- a/ironfish/src/fileStores/config.ts +++ b/ironfish/src/fileStores/config.ts @@ -148,7 +148,7 @@ export type ConfigOptions = { /** * The name of the account that the pool will use to payout from. */ - poolAccountName: string + poolAccountName?: string /** * Should pool clients be banned for perceived bad behavior @@ -316,7 +316,7 @@ export const ConfigOptionsSchema: yup.ObjectSchema> = yup minerBatchSize: YupUtils.isPositiveInteger, confirmations: YupUtils.isPositiveInteger, poolName: yup.string(), - poolAccountName: yup.string(), + poolAccountName: yup.string().optional(), poolBanning: yup.boolean(), poolHost: yup.string().trim(), poolPort: YupUtils.isPort, @@ -408,7 +408,7 @@ export class Config extends KeyStore { blocksPerMessage: 25, minerBatchSize: 25000, poolName: 'Iron Fish Pool', - poolAccountName: 'default', + poolAccountName: undefined, poolBanning: true, poolHost: DEFAULT_POOL_HOST, poolPort: DEFAULT_POOL_PORT, diff --git a/ironfish/src/mining/poolShares.test.ts b/ironfish/src/mining/poolShares.test.ts index 6cf0ef81af..84bfce6916 100644 --- a/ironfish/src/mining/poolShares.test.ts +++ b/ironfish/src/mining/poolShares.test.ts @@ -7,6 +7,7 @@ import { LogLevel } from 'consola' import { Assert } from '../assert' import { createRootLogger } from '../logger' import { createRouteTest } from '../testUtilities/routeTest' +import { Account } from '../wallet' import { MiningPoolShares } from './poolShares' describe('poolShares', () => { @@ -271,4 +272,46 @@ describe('poolShares', () => { jest.useRealTimers() }) + + describe('sendTransaction', () => { + let defaultAccount: Account | null + + beforeEach(() => { + defaultAccount = routeTest.node.wallet.getDefaultAccount() + }) + + afterEach(async () => { + await routeTest.node.wallet.setDefaultAccount(defaultAccount?.name ?? null) + }) + + it('throws an error if no account exists with accountName', async () => { + shares['accountName'] = 'fakeAccount' + + const output = { + publicAddress: 'testPublicAddress', + amount: '42', + memo: 'for testing', + assetId: 'testAsset', + } + + await expect(shares.sendTransaction([output])).rejects.toThrow( + new RegExp('No account with name'), + ) + }) + + it('throws an error if node has no default account', async () => { + await routeTest.node.wallet.setDefaultAccount(null) + + const output = { + publicAddress: 'testPublicAddress', + amount: '42', + memo: 'for testing', + assetId: 'testAsset', + } + + await expect(shares.sendTransaction([output])).rejects.toThrow( + new RegExp('No account is currently active on the node'), + ) + }) + }) }) diff --git a/ironfish/src/mining/poolShares.ts b/ironfish/src/mining/poolShares.ts index 4b727fdae9..e78af26aaa 100644 --- a/ironfish/src/mining/poolShares.ts +++ b/ironfish/src/mining/poolShares.ts @@ -22,7 +22,7 @@ export class MiningPoolShares { private poolName: string private recentShareCutoff: number - private accountName: string + private accountName: string | undefined private constructor(options: { db: PoolDatabase @@ -277,8 +277,22 @@ export class MiningPoolShares { assetId: string }[], ): Promise { + let account = this.accountName + + if (account === undefined) { + const defaultAccount = await this.rpc.getDefaultAccount() + + if (!defaultAccount.content.account) { + throw Error( + `No account is currently active on the node. Cannot sned a payout transaction.`, + ) + } + + account = defaultAccount.content.account.name + } + const transaction = await this.rpc.sendTransaction({ - account: this.accountName, + account, outputs, fee: outputs.length.toString(), expirationDelta: this.config.get('transactionExpirationDelta'), From 06c4f3515eccd248a17263ccf950ba04a7a9c3be Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Tue, 28 Mar 2023 17:41:39 -0400 Subject: [PATCH 18/21] feat(ironfish): Add `sequence` to `wallet/getAccountTransactions` (#3699) * feat(ironfish): Add `sequence` to `wallet/getAccountTransactions` * refactor(ironfish): Clean up `getTransactions` --- .../getTransactions.test.ts.fixture | 166 ++++++++++++++++++ .../rpc/routes/wallet/getTransactions.test.ts | 119 +++++++++++++ .../src/rpc/routes/wallet/getTransactions.ts | 9 +- .../__fixtures__/account.test.ts.fixture | 107 +++++++++++ ironfish/src/wallet/account.test.ts | 46 +++++ ironfish/src/wallet/account.ts | 12 ++ 6 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 ironfish/src/rpc/routes/wallet/__fixtures__/getTransactions.test.ts.fixture create mode 100644 ironfish/src/rpc/routes/wallet/getTransactions.test.ts diff --git a/ironfish/src/rpc/routes/wallet/__fixtures__/getTransactions.test.ts.fixture b/ironfish/src/rpc/routes/wallet/__fixtures__/getTransactions.test.ts.fixture new file mode 100644 index 0000000000..525e32f09a --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/__fixtures__/getTransactions.test.ts.fixture @@ -0,0 +1,166 @@ +{ + "Route wallet/getAccountTransactions streams the associated transaction for a hash": [ + { + "version": 1, + "id": "e6820aae-fdb7-4f7e-ad37-4577d35d34f7", + "name": "test", + "spendingKey": "46adeed9a5fc9854c8c757f32a865b38d6befecb817569f5078f9336bbcb2518", + "viewKey": "fb951df8a32c53e9b472ea2ed2f44cefd4bdb653ce54f0604d3a7ad1d3ae872f53f5fd5850583c3ad6cdcd32aff16c997379c7fce0ac1231d513edec91ba9aab", + "incomingViewKey": "d198e1cf3763f7344ef42f193e2117f5040bdb9b432fb289d03d0a999beb8b07", + "outgoingViewKey": "21818419721f891ee52a7c3a952a02ac824d4760324c515a8e24269a6ef6bb41", + "publicAddress": "4683eb58cde053d64008f4620c0e1b3064e245600478c15203d2dfb46cb0845e", + "createdAt": "2023-03-27T20:16:01.935Z" + }, + { + "header": { + "sequence": 2, + "previousBlockHash": "AD35193FA159CFD7440A3DFDFBE7ACB720CB4A44A441AB933071E2B9EF5DE90B", + "noteCommitment": { + "type": "Buffer", + "data": "base64:cFGj8ut1SP8xlBXESnfcasMbc+6NfOnQdPyjNnLspzk=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:Qg931VQjt6K+YYgJhLr4mRHFrEkXGNg2HLq2wBBmZbw=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1679948162767, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 4, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAkUVXL6bVAz64EgrJLj0qGJcgaH/qnBC3IllD+z3kGU+H6LKJeMHeA7YGQKght9VsFDaG5sRXvZ0ucsDEkVaO9FTjjcDB6i+XrFf/f1VzFCiQLjxB3zVF4N4USOl/h+3tJ34imK4Qpq47tR6mC3Zokj9ddvvYVB2gxAZIhka9ZzEH3/6rpS8ivSr9vQnzqYSdrLeq3YZU9RyQEBPE07SbbQfEZH0Lp7mu+FX/qjpANXm1gTIFOw8yb7uO/pK1zK+NdabHo+MvBhasE6FH55bZqSfGU/H0vQJY66bA5LqfUkbNrYRk7rGj98C1KqS03DJNIK0MxwIEhXRYEKNNxGLbF+bmxy7GUWSHoAFR+Wxg1lvUB0hxMXFNQfxOk5PgZuIrwNY6Vf0/3pEu4NnH+23g67nw8kvAoCtuw6mqvAbj1kROaoCPRcSKXMebXypy1FBqMHoomwCRugmFPGA+1yoR9bHCMytSq9ZeblyvDHn5/EoyTG+w119pDZ6ihjYDiyR6PRkZTTzAq9g0/E6U3CrqyMsSrAr4PQbZGg2OH6hZVfyfgqIOR3SsXFjcscrUVSzAywtj7tnlFskar463Ao7k7zmsVrReNOaE3M7NKcV1pZRINwi/0SYTCUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwfKRNJDYkoUAbpBbaEBUKBa9l8e7gwl2N46384PU5bqb/8tgqlmE/prQx1aKbmg0Qp5wU5qbqcJTqj/iFoVICCg==" + } + ] + } + ], + "Route wallet/getAccountTransactions throws a ValidationError for invalid sequences": [ + { + "version": 1, + "id": "6501d007-44a7-450e-b8f0-d055e9db17ec", + "name": "invalid-sequence", + "spendingKey": "5ccadae2a6e6110bab6aed559ec39c3157926131fe25e51efd437934b1eadee3", + "viewKey": "0ff3ac0f0578cf0f8b96ab3de8f54f3f74f43c635f4b95fa66253c6eb5d089b9d10afa510d58a5b46d8d0864d63cd8531090fd8b9ea4309b95eaf75beccab3cb", + "incomingViewKey": "2711cbdfa86672463795b75bac77477071815b8762c0fa243e65f446fa318a06", + "outgoingViewKey": "1b1356b74634482e815742aa98658aa8495defe69a1074367819e3d66a70c27a", + "publicAddress": "a95fdcee694e300717477a970bb516549e3c37948a5a456e4dae4ba22d29ac84", + "createdAt": "2023-03-27T20:22:20.665Z" + } + ], + "Route wallet/getAccountTransactions streams back transactions for a given block sequence": [ + { + "version": 1, + "id": "e0f23e23-3adf-4c11-bf7c-dca8fe57b5b1", + "name": "valid-sequence", + "spendingKey": "b55766d1aa6a79941b54f06748860d8cb7c2f447716a1abcc8348932e8303ff2", + "viewKey": "eadcd8e6b7001ca44260e339b6534bdb250848735cdb3a5bd975f2ff252a4386937f8f07402bc7dbd6ce3058b1206ccc1f3a367ab845b5e7d629f98c4c38048e", + "incomingViewKey": "7096c396949106cee695136e3429c4511093624a499a04ac701182ead15c8903", + "outgoingViewKey": "40b83f94de4c1bd9915623ce5320a310105fe3bb0df0c1ac0b26a2415d16a9f6", + "publicAddress": "0f6e67528f733e8a043f29862a62e6c6e880f3fbc9f8ba5e218a25475f09a568", + "createdAt": "2023-03-27T20:28:00.063Z" + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXAndxXKxlFxKHXi0W/9W0QeAVt/4pOZyjl/ENWieiCnDo4eoN/fpczM9UwWipcPskKBiHzGQlxWL0Mw+624qJRziL02bHXyjjeRlw0+qEWjrO1RoPflb1Bxh1FVktS/9GJTrxJ7MyYiFwUa8CWU1iS6YoeoUmh3fvdG5VNS5j8TeMB6SvKkjVOgY5Z9n2tA3LSDaFEHU4w2h40lniOZGFqvdXZMo7CaXKuo+KdsqCKH6t1joeAcRcGsH7vau81gDYeahvb/Qwz5fJYrWo6/9Sh0DLjJjczQTJlHaHLbjbMvpaaTzfOiUk8r1h64irRt+QIxUPMeuqvUIJJ7iYP22u8SNfV3MEGy/NKmp6Uyyebg4NrmXyucavcykZqUNVcck0MFPFhAK3fNZjvCWGe6o8F23j6gAvPS2iQQHA72lgwqZ9BNdnECeeft1tJP37nbGbYVUyyqy47rqBJylAmmnaYPsGkQJ7x2BsiotpaNi/dyGMW2misue6C1QLn08qLRXWIsN8JdMGRCWPeoez6cToZHerQ3wIz4bcR/Y0/rq6hA+6AUai9YpxCw7nu5UxdcEmOwUgaUzoIZfUpbam+vLYj0hyzsbKPgrKQypE8GPK75lTbpiOsReF6GQgFrE53rkMK278tnXjpmExV1IxQnwaGfKmJMIc4Bv4V+2un8abxeM+Gdvuq00u8XQTgzSUDSUOQEJCQ+g5pDYPptqZVnFNY3HqjYBC3TgPNUJRAvGHE1kvcpIJaBZGUfngCXjJ8soL+N+q8Pv8IjWDDjZUeo3KiHFQj35vckqycNVcrO/yfijk6XRSDRddM2LLlK5ymdrWX7aqEU/6H9BWg5vhWUSxjWjfwOILtCAI/3roG1gmSneL0iSur0WhfpUbqsFuXZcf0L8j/JKcj5uOOUr+v+Uf7dBjoKvtDBjmetF6jjwwUhqI9ktHaBwrbIi4ycVM24SXrG9xW+Hu0NMCyQ6jpGjQpUIpaXWFgSD25nUo9zPooEPymGKmLmxuiA8/vJ+LpeIYolR18JpWhhc3NldAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAA8jjq8G3VCfy9/AHJVx0ujh1CGz2oVGoPqPAvzOKQipLFsjoaxGnUpF7MPY6faSsoF2t4voAkoPa6NQ/KPZNUGhDeuaEb5urN+G1mAXGvNOLyAjyBGWw9S8IdBraQGKVBadb41+KZhjuzQ46wvwjCBJ5Q769rYZ6crknbDeeiHCQ==" + }, + { + "header": { + "sequence": 3, + "previousBlockHash": "ED964164271306033EDE335A790187B1A8CFE1CB9C98EF1D3C29E4E137B36D3F", + "noteCommitment": { + "type": "Buffer", + "data": "base64:3GBQZYWT51ILz91p1VwGEuhzVbTb2gk5NCuuwmYAAVA=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:TRhE1SC9sLWsOuv2GQ4sA/F6fRwutbFAwG0oi3/q180=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1679948881317, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 6, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAABzg9kMXIkUMO5MadBZFA3n/GCEHY1JA9AbAOouvFywujG/KtuEjIN5OKkACS+DqngjwVUt1CY+O9zRSH2l5qpc4gXbKwFVzqxwCNqKw8lNKJ3lYdDZtkjFKi9L/jyFsK0AHTsVve7KsZ9LlYqaErwu/MWXw6qZIwTYJ5sMgEIiAHARBbg9jR0RDfgkJDkD1vag4nTfwoT36OoPEVe/MCXXIXZr3e47IA/KFEExmme+WSLxndtf96oFnK1Pou5wcP1hUQuEhR/Ly1W75oReQ1mR2bT2rxqFrH6f4xGbmvFwBbare5xAXLXynJnAhsAsg0ksijV4I7VhEdxR0pIfwNGYjtLcW66GPt9GKPGu9/meKadCLPSyKGpJNSIdI9SJ9AabXu9o5S4MgYD+mbUl5PBYc+geu0ZlBU5RYtsF/gGre4hWkdbjr+UFjZn41zQs2GpUfTU8vLedrnLe5+JQ1T9Rl1Ny5DbGb2VsaflcKT5XbsZXSUnY0sTM+PrVkHI5WDLN4DB8VwS+A9vr4sQkIWpmIAst6iXOJr8LejUODjijbJV05Lv2BFWftVAAoGM0mLH2uxoJiT/Fu8yJSMuAPwdKYXj18qvdV5FDwLI9AaPj2fR7JHU6LAzElyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwKyyO5I7zvsXWdbql4qDucBvGrrbqh0e5ElmH8Q13Koxco1Qq7+YghZxbktvPmNU+2frl8SZpUHfLaPNyouQ6BA==" + }, + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXAndxXKxlFxKHXi0W/9W0QeAVt/4pOZyjl/ENWieiCnDo4eoN/fpczM9UwWipcPskKBiHzGQlxWL0Mw+624qJRziL02bHXyjjeRlw0+qEWjrO1RoPflb1Bxh1FVktS/9GJTrxJ7MyYiFwUa8CWU1iS6YoeoUmh3fvdG5VNS5j8TeMB6SvKkjVOgY5Z9n2tA3LSDaFEHU4w2h40lniOZGFqvdXZMo7CaXKuo+KdsqCKH6t1joeAcRcGsH7vau81gDYeahvb/Qwz5fJYrWo6/9Sh0DLjJjczQTJlHaHLbjbMvpaaTzfOiUk8r1h64irRt+QIxUPMeuqvUIJJ7iYP22u8SNfV3MEGy/NKmp6Uyyebg4NrmXyucavcykZqUNVcck0MFPFhAK3fNZjvCWGe6o8F23j6gAvPS2iQQHA72lgwqZ9BNdnECeeft1tJP37nbGbYVUyyqy47rqBJylAmmnaYPsGkQJ7x2BsiotpaNi/dyGMW2misue6C1QLn08qLRXWIsN8JdMGRCWPeoez6cToZHerQ3wIz4bcR/Y0/rq6hA+6AUai9YpxCw7nu5UxdcEmOwUgaUzoIZfUpbam+vLYj0hyzsbKPgrKQypE8GPK75lTbpiOsReF6GQgFrE53rkMK278tnXjpmExV1IxQnwaGfKmJMIc4Bv4V+2un8abxeM+Gdvuq00u8XQTgzSUDSUOQEJCQ+g5pDYPptqZVnFNY3HqjYBC3TgPNUJRAvGHE1kvcpIJaBZGUfngCXjJ8soL+N+q8Pv8IjWDDjZUeo3KiHFQj35vckqycNVcrO/yfijk6XRSDRddM2LLlK5ymdrWX7aqEU/6H9BWg5vhWUSxjWjfwOILtCAI/3roG1gmSneL0iSur0WhfpUbqsFuXZcf0L8j/JKcj5uOOUr+v+Uf7dBjoKvtDBjmetF6jjwwUhqI9ktHaBwrbIi4ycVM24SXrG9xW+Hu0NMCyQ6jpGjQpUIpaXWFgSD25nUo9zPooEPymGKmLmxuiA8/vJ+LpeIYolR18JpWhhc3NldAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1ldGFkYXRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAA8jjq8G3VCfy9/AHJVx0ujh1CGz2oVGoPqPAvzOKQipLFsjoaxGnUpF7MPY6faSsoF2t4voAkoPa6NQ/KPZNUGhDeuaEb5urN+G1mAXGvNOLyAjyBGWw9S8IdBraQGKVBadb41+KZhjuzQ46wvwjCBJ5Q769rYZ6crknbDeeiHCQ==" + } + ] + } + ], + "Route wallet/getAccountTransactions streams back all transactions by default": [ + { + "version": 1, + "id": "79438f9b-8b37-4ff6-a8fe-9db20974f82c", + "name": "default-stream", + "spendingKey": "208609ffb91991c207309dc455b3ba19a245d26af86f144cd30df12a1524039c", + "viewKey": "6f4bfd8852d2aedf812b08ddaf21fc6f1500cedefe3702bb67179f83479ce2b4ee032ea34c02e4245a351a7dae5f5199e526de5f2c9c4021ef875b1de5d6c0d4", + "incomingViewKey": "b004932248a2a8ee629ecc832fdb0eb91bfcbb6414c686309b3807dd39fc5803", + "outgoingViewKey": "bfe7b58879035b50397d0d924b7a9bb31cda087ae2e2bbdba1aa90adfb4f3400", + "publicAddress": "77cb8adcd0defbd757d45ef7d44e311f9109e170fa7fa18f83e5c3d0c06b5f23", + "createdAt": "2023-03-27T20:29:58.639Z" + }, + { + "header": { + "sequence": 4, + "previousBlockHash": "E730EB645A3B6D4058AFC653049FDCD3D8C80063C25586C8B941856FD34D8B41", + "noteCommitment": { + "type": "Buffer", + "data": "base64:aEBhNjfhS1hh49pWgKfk4orRWf+kF+G7Exnm92Rvf00=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:qdKhvfpoY7xN/dQBUHawvS2CsFnF5eTu1bCxU03cjJU=" + }, + "target": "883423532389192164791648750371459257913741948437809479060803100646309888", + "randomness": "0", + "timestamp": 1679948999135, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 7, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAEyLBvMmf0QG3qRI0IktY4rYoyb66AWLyW5/mqJuzhoeSx1idi2iB3YZXK2AuL6CuKQbdvqWg8EpAU0hh0YFg5GfSFT463X7qKj479SfciKOVlbK0R+AbVWbXWPzGmdqy7/PVpeFQHNkB8/w7J/kxK29uX7qjZV2mk+QSRfsnDdwVPC0+Vhfl7FiUF5C7LpZHW0VkwxoK9rWWf6G6rARqQuO+KH40FzULFjZ1TcPGUyqTqOPfwfAbnQ8pLm7k6cCNf8ivUWeKg/aLDQchLtFXf/B2/rtfMO1bNLnqQpMA2X8kx/0tuO5wgPkWhKrukxkiIsSxFaGCmYW49TWt6suONpo4EpzU/OOMUzavEPRso+0YBgOaLaQiBLTTw0jdRbMhu1C/JZ4Is+l0LEdC508NPh/9i+ejFrkO+HebTuKgJOuVIX5WRIRV2Z4eQfeE76Fdhne5zD0G4puCxhXqTEAjQBC7HcXgk7vQTqMgeeCLrT6WK4JhzVYrmmIr1j//xqlVPH89x8xAkEJBpfwWo/PgxvJ9rIaNienLQHv3A8cXn4d04xGZlsV6jz8PFlUGN3tv3aj9fIJtWp8WtyKMpAVBz9Q/DavMpZxGrBmxwjYCXzzBWb9E7Rkv9klyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwQPxsFqBJBgbjXfFHPeSzFJVMOR/I2miJwdMkTKo73Ygcdd4lQKI0qip+UM1ODkeLPiCIbJEW36fV859h1HwGAw==" + } + ] + }, + { + "header": { + "sequence": 5, + "previousBlockHash": "7DE275461320743CCC04FC0374BA437AFA5D3F88C2D990B2724041796A6965A3", + "noteCommitment": { + "type": "Buffer", + "data": "base64:jPMxqB1ne8nbdwlOof4213y68cVbeadrHXLWTM0yqT4=" + }, + "transactionCommitment": { + "type": "Buffer", + "data": "base64:8ilAjY9/6qE+r1BjAINsNPT1S5SvhSYplVQsLT1uPCI=" + }, + "target": "880842937844725196442695540779332307793253899902937591585455087694081134", + "randomness": "0", + "timestamp": 1679948999649, + "graffiti": "0000000000000000000000000000000000000000000000000000000000000000", + "noteSize": 8, + "work": "0" + }, + "transactions": [ + { + "type": "Buffer", + "data": "base64:AQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGzKiP////8AAAAAa6pyyUvMAWXtIqt0xd17vhKSD6f06ADh7EaLy1EhILqN5ewMcgX5uu2uo7BJ3TwYHMayj8/5zdXgJn0eva8oNP6FDDnoRHAanKvwILB7SU+HZ4WJcsAKcF2pUBLACSjZBPWoIGUctyLoOUwJfVUup+ABDKIFjR2JeNc1zEorBzsNMZk7w6X4/O1p4yUzoP1ke/1PPsyvf0BnoDRhxliwc2iLoFN65aV26+jX1b1yheGicTpuxy5JxT1h+q/eAnBgWVGZfNHr3XfQfi4LSuir2NE7FwV21ECU+l5DrS4n88MWyhYYchYlHlq2yxXZnYiBh/5RArsEyZy5Hn6GNG+qrETd+DpYq1EliYyoafev+FX8roMPs0AeUmjMKD62RhkGqS2knPi1p5XG6MJ3DDtIGm8SAEesDNLlJ/sYbzjvf9HbsNWGNFzxBNtbtbs623drhRsduALr9PUbXdCh7StNFcNCLAef9vKr79uudE4S+5nP2UkJrW3wOg5tLMwh1GPYDlMWWqhyILLp2RpWpV7WYmRctREdl1QozhuocQhtS05o8PukcMl1I+cMSh4IYMHeSUoaaD2bE+GHH7XcPT6fKni7+EEgEtYWtQ46sVD9jCbRHulheNUpZUlyb24gRmlzaCBub3RlIGVuY3J5cHRpb24gbWluZXIga2V5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw2R2UwFLgtsGH3Rbwh5nNMkey0Lgab4OTkGzWI+Nz+cDL/TkSdnYFuTD14PRMMKREA0Wn6wRIt3g4GrmvlGQ/CA==" + } + ] + } + ] +} \ No newline at end of file diff --git a/ironfish/src/rpc/routes/wallet/getTransactions.test.ts b/ironfish/src/rpc/routes/wallet/getTransactions.test.ts new file mode 100644 index 0000000000..6c3d6fcdf4 --- /dev/null +++ b/ironfish/src/rpc/routes/wallet/getTransactions.test.ts @@ -0,0 +1,119 @@ +/* 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 { + useAccountFixture, + useMinerBlockFixture, + usePostTxFixture, +} from '../../../testUtilities' +import { createRouteTest } from '../../../testUtilities/routeTest' +import { AsyncUtils } from '../../../utils' +import { GetAccountTransactionsResponse } from './getTransactions' + +describe('Route wallet/getAccountTransactions', () => { + const routeTest = createRouteTest(true) + + it('streams the associated transaction for a hash', async () => { + const node = routeTest.node + const account = await useAccountFixture(node.wallet) + + const block = await useMinerBlockFixture(routeTest.chain, undefined, account, node.wallet) + await expect(node.chain).toAddBlock(block) + await node.wallet.updateHead() + + const response = routeTest.client.request( + 'wallet/getAccountTransactions', + { + account: account.name, + hash: block.transactions[0].hash(), + }, + ) + + const transactions = await AsyncUtils.materialize(response.contentStream()) + expect(transactions).toHaveLength(1) + expect(transactions[0].hash).toEqual(block.transactions[0].hash().toString('hex')) + }) + + it('throws a ValidationError for invalid sequences', async () => { + const node = routeTest.node + const account = await useAccountFixture(node.wallet, 'invalid-sequence') + + const response = routeTest.client + .request('wallet/getAccountTransactions', { + account: account.name, + sequence: 0, + }) + .waitForEnd() + + await expect(response).rejects.toMatchObject({ + status: 400, + }) + }) + + it('streams back transactions for a given block sequence', async () => { + const node = routeTest.node + const account = await useAccountFixture(node.wallet, 'valid-sequence') + + const asset = new Asset(account.spendingKey, 'asset', 'metadata') + const mint = await usePostTxFixture({ + node: node, + wallet: node.wallet, + from: account, + mints: [ + { + name: asset.name().toString('utf8'), + metadata: asset.metadata().toString('utf8'), + value: BigInt(10), + }, + ], + }) + + const block = await useMinerBlockFixture(routeTest.chain, undefined, account, node.wallet, [ + mint, + ]) + await expect(node.chain).toAddBlock(block) + await node.wallet.updateHead() + + const response = routeTest.client.request( + 'wallet/getAccountTransactions', + { + account: account.name, + sequence: block.header.sequence, + }, + ) + + const blockTransactionHashes = block.transactions + .map((transaction) => transaction.hash()) + .sort() + const accountTransactions = await AsyncUtils.materialize(response.contentStream()) + const accountTransactionHashes = accountTransactions + .map(({ hash }) => Buffer.from(hash, 'hex')) + .sort() + + expect(accountTransactionHashes).toEqual(blockTransactionHashes) + }) + + it('streams back all transactions by default', async () => { + const node = routeTest.node + const account = await useAccountFixture(node.wallet, 'default-stream') + + const blockA = await useMinerBlockFixture(routeTest.chain, undefined, account, node.wallet) + await expect(node.chain).toAddBlock(blockA) + await node.wallet.updateHead() + + const blockB = await useMinerBlockFixture(routeTest.chain, undefined, account, node.wallet) + await expect(node.chain).toAddBlock(blockB) + await node.wallet.updateHead() + + const response = routeTest.client.request( + 'wallet/getAccountTransactions', + { + account: account.name, + }, + ) + + const transactions = await AsyncUtils.materialize(response.contentStream()) + expect(transactions).toHaveLength(2) + }) +}) diff --git a/ironfish/src/rpc/routes/wallet/getTransactions.ts b/ironfish/src/rpc/routes/wallet/getTransactions.ts index ddd5dd1d7b..87331e8b03 100644 --- a/ironfish/src/rpc/routes/wallet/getTransactions.ts +++ b/ironfish/src/rpc/routes/wallet/getTransactions.ts @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import * as yup from 'yup' import { IronfishNode } from '../../../node' +import { GENESIS_BLOCK_SEQUENCE } from '../../../primitives' import { TransactionStatus, TransactionType } from '../../../wallet' import { Account } from '../../../wallet/account' import { TransactionValue } from '../../../wallet/walletdb/transactionValue' @@ -14,6 +15,7 @@ import { getAccount } from './utils' export type GetAccountTransactionsRequest = { account?: string hash?: string + sequence?: number limit?: number offset?: number confirmations?: number @@ -38,6 +40,7 @@ export const GetAccountTransactionsRequestSchema: yup.ObjectSchema { expect(accountBTx[1].transaction.hash()).toEqualHash(sortedHashes[1]) }) }) + + describe('getTransactionsBySequence', () => { + it('returns a stream of transactions with a matching block sequence', async () => { + const { node } = nodeTest + const account = await useAccountFixture(node.wallet) + + const minerBlockA = await useMinerBlockFixture( + node.chain, + undefined, + account, + node.wallet, + ) + await node.chain.addBlock(minerBlockA) + await node.wallet.updateHead() + + const minerBlockB = await useMinerBlockFixture( + node.chain, + undefined, + account, + node.wallet, + ) + await node.chain.addBlock(minerBlockB) + await node.wallet.updateHead() + + const transactionA = await useTxFixture(node.wallet, account, account) + const transactionB = await useTxFixture(node.wallet, account, account) + + const block = await useMinerBlockFixture(node.chain, undefined, account, node.wallet, [ + transactionA, + transactionB, + ]) + await node.chain.addBlock(block) + await node.wallet.updateHead() + + const blockTransactionHashes = block.transactions + .map((transaction) => transaction.hash()) + .sort() + const accountTransactions = await AsyncUtils.materialize( + account.getTransactionsBySequence(block.header.sequence), + ) + const accountTransactionHashes = accountTransactions + .map(({ transaction }) => transaction.hash()) + .sort() + expect(accountTransactionHashes).toEqual(blockTransactionHashes) + }) + }) }) diff --git a/ironfish/src/wallet/account.ts b/ironfish/src/wallet/account.ts index 9299818cf5..dd9feb1412 100644 --- a/ironfish/src/wallet/account.ts +++ b/ironfish/src/wallet/account.ts @@ -686,6 +686,18 @@ export class Account { return this.walletDb.loadTransactionsByTime(this, tx) } + async *getTransactionsBySequence( + sequence: number, + tx?: IDatabaseTransaction, + ): AsyncGenerator> { + for await (const { + hash: _hash, + ...transaction + } of this.walletDb.loadTransactionsInSequenceRange(this, sequence, sequence, tx)) { + yield transaction + } + } + async *getTransactionsOrderedBySequence( tx?: IDatabaseTransaction, ): AsyncGenerator> { From 583c2a44e059dd0408da1b3278d9cd9f4487da18 Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Tue, 28 Mar 2023 17:52:28 -0400 Subject: [PATCH 19/21] feat(cli): Add `sequence` to `wallet:transactions` (#3700) * feat(ironfish): Add `sequence` to `wallet/getAccountTransactions` * feat(cli): Add `sequence` to `wallet:transactions` --- ironfish-cli/src/commands/wallet/transactions.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ironfish-cli/src/commands/wallet/transactions.ts b/ironfish-cli/src/commands/wallet/transactions.ts index cd323ab604..f2146d20d5 100644 --- a/ironfish-cli/src/commands/wallet/transactions.ts +++ b/ironfish-cli/src/commands/wallet/transactions.ts @@ -24,6 +24,10 @@ export class TransactionsCommand extends IronfishCommand { char: 't', description: 'Transaction hash to get details for', }), + sequence: Flags.integer({ + char: 's', + description: 'Block sequence to get transactions for', + }), limit: Flags.integer({ description: 'Number of latest transactions to get details for', }), @@ -52,6 +56,7 @@ export class TransactionsCommand extends IronfishCommand { const response = client.getAccountTransactionsStream({ account, hash: flags.hash, + sequence: flags.sequence, limit: flags.limit, offset: flags.offset, confirmations: flags.confirmations, From 9b7f92bb0f1bbbe3c49533325fd7f8d432238acb Mon Sep 17 00:00:00 2001 From: Hugh Cunningham <57735705+hughy@users.noreply.github.com> Date: Tue, 28 Mar 2023 14:56:31 -0700 Subject: [PATCH 20/21] checks if pool account exists on start if payouts enabled (#3704) * checks if pool account exists on start if payouts enabled throws an error while starting a mining pool if payouts are enabled but there isn't an account to make payouts from. - checks for account by name if poolAccountName is configured or checks for default account otherwise - rearranges method calls in pool start to make RPC connection before starting MiningPoolShares instance * fixes lint * fixes test creates default account on node before each test --- .../__fixtures__/poolShares.test.ts.fixture | 145 ++++++++++++++++++ ironfish/src/mining/pool.ts | 6 +- ironfish/src/mining/poolShares.test.ts | 41 +++++ ironfish/src/mining/poolShares.ts | 26 +++- 4 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 ironfish/src/mining/__fixtures__/poolShares.test.ts.fixture diff --git a/ironfish/src/mining/__fixtures__/poolShares.test.ts.fixture b/ironfish/src/mining/__fixtures__/poolShares.test.ts.fixture new file mode 100644 index 0000000000..339a77789b --- /dev/null +++ b/ironfish/src/mining/__fixtures__/poolShares.test.ts.fixture @@ -0,0 +1,145 @@ +{ + "poolShares start throws an error if the pool account does not exist": [ + { + "version": 1, + "id": "5a1bdef3-2427-4af8-94ba-443749031624", + "name": "default", + "spendingKey": "531e8b773625fcd70991248249478a2de78686bd396e2895e401ec0135207ae7", + "viewKey": "017bcd397bb5ad6db4edb02178cf59395036de0699d954eeaa798fb3d936534e0fb716e51a77faa6371056682d21515571c472e44374d76a4a2bf5020426e864", + "incomingViewKey": "88cb1b16fe5378dfe71a3a1a404b3ba9bd088f5630e9f022e71ef116b77b4f02", + "outgoingViewKey": "f948035367cf01e31b3e97c27971a0017aad7f37436f5160562d7480f3b80b7d", + "publicAddress": "07078da20fabde17a57a6817a05ee831231297e6c921dd9922bb443ebb084c6e", + "createdAt": "2023-03-28T18:56:33.898Z" + } + ], + "poolShares start throws an error if the node has no default account": [ + { + "version": 1, + "id": "93f21f31-97d7-430a-9ad7-1fc317e2c95f", + "name": "default", + "spendingKey": "aff4cdeec3453ce802eb9762f81495d0ab9ad60312ba7ecd3eba7d7d38c58bec", + "viewKey": "100d2a739a2b3dae04fae8ab5df71f82b03debf7b20cbd84311f8e0fa3e3e18201ff5cd74e7cc7fa396747dc1bc97cc3e520da90a70bf6d814abc0c49d8ba6e0", + "incomingViewKey": "36771801fd9013e4ce11a7ead1f2e83e3cc3c08da2ab7bdbcfeec3320689d003", + "outgoingViewKey": "e229860319f5819e58b8924d5b1169d05a8f307f15841c3383bdc44952bda3a2", + "publicAddress": "6af923f6e13d44a8fae22353e777a810c72658ba1ff3376d82c7efda2e98c284", + "createdAt": "2023-03-28T18:56:33.998Z" + } + ], + "poolShares start does not check for the pool account if payouts are disabled": [ + { + "version": 1, + "id": "6b5fa229-6982-4f07-a5f9-517c630c2431", + "name": "default", + "spendingKey": "3fa790052a9ea30b6380eda7a919c67b402640aeb585ae2d336d8efc4a46f97f", + "viewKey": "29fac8553d9b6ddf72b599b8c6871cc60b8c83fe060fec86f667b7015a52bfbf553c25116a17e38e740acd750fc52e2d0684bbcfaa9755a780245afba02dc536", + "incomingViewKey": "98ae255eb7d2d3c3c09d28a10367266bbf4725a82403346c9c4753c424c33b04", + "outgoingViewKey": "e4e4fc1a7332caa8cd7f6445ad9d37baea77febd4e7a80369965f14cae1d77eb", + "publicAddress": "5de4ccf2b1e52ab262c94ac632185113c6cf02034bdb74cf67008a2499dd1f6a", + "createdAt": "2023-03-28T18:56:34.078Z" + } + ], + "poolShares shareRate": [ + { + "version": 1, + "id": "9f47abc6-f258-45b6-bcc3-bc7076678f09", + "name": "default", + "spendingKey": "bc8e7b69924a69b6042689eb43fe42cf6287c0e596f87f7d784f6357d36183af", + "viewKey": "0fc5b1132874aad550309c52d9bee3fea06063e035d1ce1e0d57a87d36a4e2965d12e0ec13ec020d467dfbd13c3e56af2794e279757a8f73549c58f68fa77516", + "incomingViewKey": "74988c1e95baba38d11e8480a67f6f19cb2ce48b5d6f66ce8cd0d3bc8028a505", + "outgoingViewKey": "99311f7f7fe2be6bd18273b4f5bcadd154186a0fd8fdfb98338376a34cec98ac", + "publicAddress": "09a07f0f139ba7cbb86f039b946af8323df15f6fa050a789da256077a66cb65f", + "createdAt": "2023-03-28T18:56:34.180Z" + } + ], + "poolShares rolloverPayoutPeriod": [ + { + "version": 1, + "id": "572e5be7-0ee4-48e1-982e-99c61698d8de", + "name": "default", + "spendingKey": "3d24ec97e2031f4aec7919db0cea6969449e8c9f68300a96aad7fee2a2812936", + "viewKey": "cc94ab8a41a8bd52cca923597482a393676960a7a953ae67e3e5f66bcf859f39e55fa2e59dfe4c0715914b47c39a16d44a5bed5c818bc70c40f071752a2ca4ad", + "incomingViewKey": "e54c03259c8aee414f0d8faf7c8a7e9d37f0283304a46e411b9ca9b03c721606", + "outgoingViewKey": "bec7ec205015c107d2b5ac3635b134a970f13ac06a4c2bb59cbcc73199a643ef", + "publicAddress": "7ddc397fd073a020c719bf437dbe79979e7b647a664c866adc802868a6941af2", + "createdAt": "2023-03-28T18:56:34.275Z" + } + ], + "poolShares blocks": [ + { + "version": 1, + "id": "afd3224f-3dc1-44ee-a103-62f01c65eb2b", + "name": "default", + "spendingKey": "f66ca62ad63ddfbee8412cf0f4402a2a10d3d91be8f7c010c647df54365075d0", + "viewKey": "6114342be6dc527548af63e1a4d84581558c3eb3caebff02550795e1e1461817191f8c909d24cdf78235741025bc7f938d7bac28fa8f409db0ccced384fab265", + "incomingViewKey": "57f5753ddb12221b035be29ca2d49d4d075df0ec12378a904052567c45ffda03", + "outgoingViewKey": "62212d0d55d0670ed529296cb110e432b9a13b29b8704fb1820ab27cd7c3bea7", + "publicAddress": "77e4c3c83f945116ea148635d784c8d14d15ee7301f96b8fdf9fed5e56fd7e5e", + "createdAt": "2023-03-28T18:56:34.359Z" + } + ], + "poolShares transactions expected flow": [ + { + "version": 1, + "id": "6a068041-e48a-47ad-86e6-95f2295e5cfd", + "name": "default", + "spendingKey": "32c0103a18348fb542899913584ab84a86bc98b1b9d1035ceb55f043aad22b7f", + "viewKey": "7eb871f4c0e4a44899aa091380628f3ef45dbb53a3835c0d199d175a93a1ac3246247647cac27508e81d12b3690fe632c031d78a7567966f467470b7090954a2", + "incomingViewKey": "40599e3b0557fa85897fd85f5209c251ce8f0b5be4e97900b9521609dfccf506", + "outgoingViewKey": "0c5e3032ec4d0cc8c2af88a453bc917cf14f6234af9bea7133335caa7fde80d5", + "publicAddress": "c75bd238d5a9af5892a2bdacb745ffb67ed4bbb2d2d6cfc12019b93db55865d8", + "createdAt": "2023-03-28T18:56:34.442Z" + } + ], + "poolShares transactions expired transactions should mark shares unpaid": [ + { + "version": 1, + "id": "aef2cffb-6358-4629-8f4e-917ce44b09a7", + "name": "default", + "spendingKey": "a01969d8b4dd2b4c30df8f464d3705293245ad6845ab89a9f2c6846471a6cf37", + "viewKey": "33859c56600e8ab286c061a4ec120085aff6007f775a0c374aefeec0fcfe3561af8a75b4415ba72fc2485b6f90f31b2dfb4e3a5a6dfea594246c19ba3e56846e", + "incomingViewKey": "7fbaa9930f4b483f820231b85fb85ac15c4b39407873bc2e2c7315db08291d03", + "outgoingViewKey": "17af824a371e13303a629434d00d8dc6d5aea5e3a164de58eccbdb84728acd39", + "publicAddress": "c10c2205292a593edb44537ffcf9c44cfaf92b059813cf7ed23ae578b1c10a87", + "createdAt": "2023-03-28T18:56:34.521Z" + } + ], + "poolShares createNewPayout": [ + { + "version": 1, + "id": "d712c3df-0db9-42a8-97f9-2f15a63bac1d", + "name": "default", + "spendingKey": "d23957848ffb6be3ddf61dec1ef77e4e81bec21d746758684be4e28db1e222de", + "viewKey": "2aab3e747787622e0fc1ba0098baa6a2d2b64b69b0dcb735c7ad6943aa404b2dbbf0f389467f59d85213576ffd84e607d84f33abb02afec509ad71894b6f0fcc", + "incomingViewKey": "b4a6b99d8bfbb934338213b167d7cc0a7d3dc415b18e49e2276a4efd5966f704", + "outgoingViewKey": "e9afa6665bb97ed0776fab1bb4e6416a462b8f0e4901baf5e894e17a982eacb8", + "publicAddress": "adb5f969d451c8bc61fbbdf060a9c3811f1e0c6f65d53b01e9380bcb1b9dafbe", + "createdAt": "2023-03-28T18:56:34.610Z" + } + ], + "poolShares sendTransaction throws an error if no account exists with accountName": [ + { + "version": 1, + "id": "237da764-3517-4918-ad72-1ebd266f1bfc", + "name": "default", + "spendingKey": "2e9291d86ca8923508d41415550652b6fb465981f92df1de76e944f0a07eb855", + "viewKey": "c2a51237d16d9fb227ad71220f9b60c57159de61b586c4f5d2e7a6a73e14fd6bf81566233e7720f7e1dac0827d1544d8872f4e98abc8749e47b9293f398387a9", + "incomingViewKey": "93427a5f8fe5a292c2d7aa843927defc7acc17c54ed779019d9d211d4cb30307", + "outgoingViewKey": "426ef116bdb3122db8f885c35530ca70cbb30d40ccbc6bdc982c762aef07cce3", + "publicAddress": "f6ce419f01fafbc5d0c414585ad60f119f3730bb0003a21620a01fe135ed6c64", + "createdAt": "2023-03-28T18:56:34.692Z" + } + ], + "poolShares sendTransaction throws an error if node has no default account": [ + { + "version": 1, + "id": "53dc3274-1b49-41f7-b09e-f750a6a43e0f", + "name": "default", + "spendingKey": "860c781548b3c93cedecab564c421c3e27b1fa3fd493a640ceed68ba4b20c210", + "viewKey": "c3ac1585ca579939767db6401bbd08b27632e74f4ad2a38b314db2d226d42dcbbd74bd426cfc08c9c820181d099e47159a257b221abc9bf083bf8a1e21f4bdc8", + "incomingViewKey": "00bbe37cf0c662b65da944fadf06c32b009670bd68908d2b0332d7380f1d4202", + "outgoingViewKey": "ce406ac2a1cdc9a0227dd31a8ef918440a42c6d07801fc8157c435b737d7bc9b", + "publicAddress": "8ffe4bd431a9cf0979016b124f1c78533eb3ace86f874c3bfbfd3868fd95963f", + "createdAt": "2023-03-28T18:56:34.799Z" + } + ] +} \ No newline at end of file diff --git a/ironfish/src/mining/pool.ts b/ironfish/src/mining/pool.ts index 6b6622b0f9..1685f7c6e0 100644 --- a/ironfish/src/mining/pool.ts +++ b/ironfish/src/mining/pool.ts @@ -159,7 +159,6 @@ export class MiningPool { this.stopPromise = new Promise((r) => (this.stopResolve = r)) this.started = true - await this.shares.start() this.logger.info(`Starting stratum server v${String(this.stratum.version)}`) await this.stratum.start() @@ -167,6 +166,10 @@ export class MiningPool { this.logger.info('Connecting to node...') this.rpc.onClose.on(this.onDisconnectRpc) + await this.startConnectingRpc() + + await this.shares.start() + const statusInterval = this.config.get('poolStatusNotificationInterval') if (statusInterval > 0) { this.notifyStatusInterval = setInterval( @@ -175,7 +178,6 @@ export class MiningPool { ) } - await this.startConnectingRpc() void this.eventLoop() } diff --git a/ironfish/src/mining/poolShares.test.ts b/ironfish/src/mining/poolShares.test.ts index 84bfce6916..ebcdb6f3cb 100644 --- a/ironfish/src/mining/poolShares.test.ts +++ b/ironfish/src/mining/poolShares.test.ts @@ -6,6 +6,7 @@ import { Asset } from '@ironfish/rust-nodejs' import { LogLevel } from 'consola' import { Assert } from '../assert' import { createRootLogger } from '../logger' +import { useAccountFixture } from '../testUtilities/fixtures/account' import { createRouteTest } from '../testUtilities/routeTest' import { Account } from '../wallet' import { MiningPoolShares } from './poolShares' @@ -16,6 +17,10 @@ describe('poolShares', () => { beforeEach(async () => { const logger = createRootLogger().withTag('test') + + await useAccountFixture(routeTest.node.wallet, 'default') + await routeTest.wallet.setDefaultAccount('default') + logger.level = LogLevel.Silent shares = await MiningPoolShares.init({ rpc: routeTest.client, @@ -32,6 +37,42 @@ describe('poolShares', () => { await shares.stop() }) + describe('start', () => { + let defaultAccount: Account | null + + beforeEach(() => { + defaultAccount = routeTest.node.wallet.getDefaultAccount() + }) + + afterEach(async () => { + await routeTest.node.wallet.setDefaultAccount(defaultAccount?.name ?? null) + }) + + it('throws an error if the pool account does not exist', async () => { + shares['accountName'] = 'accountDoesNotExist' + + await expect(shares.start()).rejects.toThrow(new RegExp('account not found')) + }) + + it('throws an error if the node has no default account', async () => { + await routeTest.node.wallet.setDefaultAccount(null) + + await expect(shares.start()).rejects.toThrow( + new RegExp('no account is active on the node'), + ) + }) + + it('does not check for the pool account if payouts are disabled', async () => { + shares['enablePayouts'] = false + + const accountExists = jest.spyOn(shares, 'assertAccountExists') + + await shares.start() + + expect(accountExists).not.toHaveBeenCalled() + }) + }) + it('shareRate', async () => { jest.useFakeTimers({ legacyFakeTimers: false }) diff --git a/ironfish/src/mining/poolShares.ts b/ironfish/src/mining/poolShares.ts index e78af26aaa..17f2ec994a 100644 --- a/ironfish/src/mining/poolShares.ts +++ b/ironfish/src/mining/poolShares.ts @@ -69,6 +69,10 @@ export class MiningPoolShares { } async start(): Promise { + if (this.enablePayouts) { + await this.assertAccountExists() + } + await this.db.start() } @@ -284,7 +288,7 @@ export class MiningPoolShares { if (!defaultAccount.content.account) { throw Error( - `No account is currently active on the node. Cannot sned a payout transaction.`, + `No account is currently active on the node. Cannot send a payout transaction.`, ) } @@ -300,4 +304,24 @@ export class MiningPoolShares { return transaction.content.hash } + + async assertAccountExists(): Promise { + if (this.accountName) { + const response = await this.rpc.getAccounts() + + const accountNames = response.content.accounts + + if (accountNames.find((accountName) => accountName === this.accountName) === undefined) { + throw Error( + `Cannot send pool payouts from account '${this.accountName}': account not found.`, + ) + } + } else { + const defaultAccount = await this.rpc.getDefaultAccount() + + if (defaultAccount.content.account === null) { + throw Error(`Cannot send pool payouts: no account is active on the node.`) + } + } + } } From d73b867e07e70a1a3aa57e7aba9edf5ad2cfd3ca Mon Sep 17 00:00:00 2001 From: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:59:25 -0400 Subject: [PATCH 21/21] chore(cli,ironfish): Bump CLI to 0.1.74, SDK to 0.0.51 (#3712) --- ironfish-cli/package.json | 4 ++-- ironfish/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ironfish-cli/package.json b/ironfish-cli/package.json index 7d4c4ab6bb..f39cb004e2 100644 --- a/ironfish-cli/package.json +++ b/ironfish-cli/package.json @@ -1,6 +1,6 @@ { "name": "ironfish", - "version": "0.1.73", + "version": "0.1.74", "description": "CLI for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js", @@ -60,7 +60,7 @@ "@aws-sdk/client-secrets-manager": "3.276.0", "@aws-sdk/s3-request-presigner": "3.127.0", "@ironfish/rust-nodejs": "0.1.29", - "@ironfish/sdk": "0.0.50", + "@ironfish/sdk": "0.0.51", "@oclif/core": "1.23.1", "@oclif/plugin-help": "5.1.12", "@oclif/plugin-not-found": "2.3.1", diff --git a/ironfish/package.json b/ironfish/package.json index 710e1e74e5..1e40a1bd61 100644 --- a/ironfish/package.json +++ b/ironfish/package.json @@ -1,6 +1,6 @@ { "name": "@ironfish/sdk", - "version": "0.0.50", + "version": "0.0.51", "description": "SDK for running and interacting with an Iron Fish node", "author": "Iron Fish (https://ironfish.network)", "main": "build/src/index.js",