From 1b510ae8fbb77a4c229621730d90c9211673636d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Thu, 1 Jun 2023 12:13:20 -0300 Subject: [PATCH 01/12] feat: added getAddressAtIndex method and API --- src/api/addresses.ts | 70 ++++++++++++++++++++++++++++++++++++++++++-- src/db/index.ts | 37 ++++++++++++++++++++++- src/types.ts | 4 +++ tests/db.test.ts | 38 ++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 3 deletions(-) diff --git a/src/api/addresses.ts b/src/api/addresses.ts index 9bff0a4e..45da2371 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -7,17 +7,19 @@ import 'source-map-support/register'; -import Joi from 'joi'; +import Joi, { ValidationError } from 'joi'; import { APIGatewayProxyHandler } from 'aws-lambda'; import { ApiError } from '@src/api/errors'; import { closeDbAndGetError, warmupMiddleware } from '@src/api/utils'; import { getWallet, getWalletAddresses, + getAddressAtIndex as dbGetAddressAtIndex, } from '@src/db'; -import { AddressInfo } from '@src/types'; +import { AddressInfo, AddressAtIndexRequest, Severity } from '@src/types'; import { closeDbConnection, getDbConnection } from '@src/utils'; import { walletIdProxyHandler } from '@src/commons'; +import { addAlert } from '@src/utils/alerting.utils'; import middy from '@middy/core'; import cors from '@middy/http-cors'; @@ -32,6 +34,19 @@ const checkMineBodySchema = Joi.object({ .required(), }); +class AddressAtIndexValidator { + static readonly bodySchema = Joi.object({ + index: Joi.number().min(0).required(), + }); + + static validate(payload: unknown): { value: AddressAtIndexRequest, error: ValidationError} { + return AddressAtIndexValidator.bodySchema.validate(payload, { + abortEarly: false, // We want it to return all the errors not only the first + convert: true, // We need to convert as parameters are sent on the QueryString + }) as { value: AddressAtIndexRequest, error: ValidationError }; + } +} + /* * Check if a list of addresses belong to the caller wallet * @@ -92,6 +107,57 @@ export const checkMine: APIGatewayProxyHandler = middy(walletIdProxyHandler(asyn }; })).use(cors()); +/* + * Get the addresses of a wallet + * + * This lambda is called by API Gateway on GET /addresses + */ +export const getAddressAtIndex: APIGatewayProxyHandler = middy( + walletIdProxyHandler(async (walletId, event) => { + const status = await getWallet(mysql, walletId); + + if (!status) { + return closeDbAndGetError(mysql, ApiError.WALLET_NOT_FOUND); + } + + if (!status.readyAt) { + return closeDbAndGetError(mysql, ApiError.WALLET_NOT_READY); + } + + const { value: body, error } = AddressAtIndexValidator.validate(event.pathParameters); + + if (error) { + const details = error.details.map((err) => ({ + message: err.message, + path: err.path, + })); + + return closeDbAndGetError(mysql, ApiError.INVALID_PAYLOAD, { details }); + } + + const address: AddressInfo | null = await dbGetAddressAtIndex(mysql, walletId, body.index); + + // If the walletId is valid and the wallet is ready we should have the address in our + // database. If we don't, alert. + if (!address) { + await addAlert( + 'Error on onNewTxRequest', + 'Erroed on onNewTxRequest lambda', + Severity.MINOR, + { walletId, error: `getAddressAtIndex was called with a valid walletId but could not find the address at index ${body.index}` }, + ); + } + + await closeDbConnection(mysql); + + return { + statusCode: 200, + body: JSON.stringify({ success: true, address }), + }; + }), +).use(cors()) + .use(warmupMiddleware()); + /* * Get the addresses of a wallet * diff --git a/src/db/index.ts b/src/db/index.ts index 9b58dcac..66a66ac5 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -7,7 +7,7 @@ import { strict as assert } from 'assert'; import { ServerlessMysql } from 'serverless-mysql'; import { get } from 'lodash'; -import { OkPacket } from 'mysql'; +import { OkPacket } from 'mysql2'; import { constants } from '@hathor/wallet-lib'; import { AddressIndexMap, @@ -3157,3 +3157,38 @@ export const getUnsentTxProposals = async ( return result.map((row) => row.id); }; + +/** + * Gets a specific address from an index and a walletId + * + * @param mysql - Database connection + * @param walletId - The wallet id to search for + * @param index - The address index to search for + * + * @returns An object containing the address, its index and the number of transactions + */ +export const getAddressAtIndex = async ( + mysql: ServerlessMysql, + walletId: string, + index: number, +): Promise => { + const addresses = await mysql.query( + ` + SELECT \`address\`, \`index\`, \`transactions\` + FROM \`address\` pd + WHERE \`index\` = ? + AND \`wallet_id\` = ? + LIMIT 1`, + [walletId, index], + ); + + if (addresses.length <= 0) { + return null; + } + + return { + address: addresses[0].address as string, + index: addresses[0].index as number, + transactions: addresses[0].transactions as number, + } as AddressInfo; +}; diff --git a/src/types.ts b/src/types.ts index 8d66e56a..cf03b687 100644 --- a/src/types.ts +++ b/src/types.ts @@ -709,6 +709,10 @@ export interface PushDelete { deviceId: string, } +export interface AddressAtIndexRequest { + index: number, +} + export interface TxByIdRequest { txId: string, } diff --git a/tests/db.test.ts b/tests/db.test.ts index e8a819df..3d2a451b 100644 --- a/tests/db.test.ts +++ b/tests/db.test.ts @@ -28,6 +28,7 @@ import { getTxOutput, getTransactionsById, getTxsAfterHeight, + getAddressAtIndex, initWalletBalance, initWalletTxHistory, markUtxosWithProposalId, @@ -104,6 +105,7 @@ import { PushProvider, Severity, Block, + AddressInfo, } from '@src/types'; import { closeDbConnection, @@ -3615,3 +3617,39 @@ describe('Clear unsent txProposals utxos', () => { expect(logger.debug).toHaveBeenCalledWith('No txproposals utxos to clean.'); }); }); + +describe('getAddressByIndex', () => { + it('should find a wallets address from its index', async () => { + expect.hasAssertions(); + + const address = 'address'; + const walletId = 'walletId'; + const index = 0; + const transactions = 0; + + await addToAddressTable(mysql, [{ + address, + index, + walletId, + transactions, + }]); + + await expect(getAddressAtIndex(mysql, walletId, index)) + .resolves + .toStrictEqual({ + address, + index, + transactions, + }); + }); + + it('should return null if an address couldnt be found', async () => { + expect.hasAssertions(); + + const walletId = 'walletId'; + + await expect(getAddressAtIndex(mysql, walletId, 1)) + .resolves + .toBeNull(); + }); +}); From a02b24d3e3f426b635476487d7df602a30309af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Thu, 1 Jun 2023 12:16:45 -0300 Subject: [PATCH 02/12] feat: added index to address index column --- .../20230601151507-address_index_idx.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 db/migrations/20230601151507-address_index_idx.js diff --git a/db/migrations/20230601151507-address_index_idx.js b/db/migrations/20230601151507-address_index_idx.js new file mode 100644 index 00000000..b1105f6f --- /dev/null +++ b/db/migrations/20230601151507-address_index_idx.js @@ -0,0 +1,17 @@ +'use strict'; + +module.exports = { + up: async (queryInterface) => { + await queryInterface.addIndex( + 'address', + ['index'], { + name: 'address_index_idx', + fields: 'index', + }, + ); + }, + + down: async (queryInterface) => { + await queryInterface.removeIndex('address', 'address_index_idx'); + }, +}; From 26f0138e7c834bfc980fcecf3d00b668f612700c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Thu, 1 Jun 2023 12:17:26 -0300 Subject: [PATCH 03/12] chore: added flake to the project --- .envrc | 7 +++++ .gitignore | 2 ++ flake.lock | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 24 ++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..7a65628c --- /dev/null +++ b/.envrc @@ -0,0 +1,7 @@ +if [[ $(type -t use_flake) != function ]]; then + echo "ERROR: use_flake function missing." + echo "Please update direnv to v2.30.0 or later." + exit 1 +fi + +use flake diff --git a/.gitignore b/.gitignore index b403349d..25b47712 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ coverage # fcm account config **/fcm.config.json + +.direnv diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..1c73b027 --- /dev/null +++ b/flake.lock @@ -0,0 +1,92 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1650201426, + "narHash": "sha256-u43Xf03ImFJWKLHddtjOpCJCHbuM0SQbb6FKR5NuFhk=", + "owner": "numtide", + "repo": "devshell", + "rev": "cb76bc75a0ee81f2d8676fe681f268c48dbd380e", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1642700792, + "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1649676176, + "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1643381941, + "narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1650194139, + "narHash": "sha256-kurZsqeOw5fpqA/Ig+8tHvbjwzs5P9AE6WUKOX1m6qM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bd4dffcdb7c577d74745bd1eff6230172bd176d5", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..9d2f2498 --- /dev/null +++ b/flake.nix @@ -0,0 +1,24 @@ +{ + description = "virtual environments"; + + inputs.devshell.url = "github:numtide/devshell"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + + outputs = { self, flake-utils, devshell, nixpkgs }: + + flake-utils.lib.eachDefaultSystem (system: { + devShell = + let pkgs = import nixpkgs { + inherit system; + + overlays = [ devshell.overlay ]; + }; + in + pkgs.devshell.mkShell { + packages = with pkgs; [ + nixpkgs-fmt + nodejs-14_x + ]; + }; + }); +} From 246cbb490f813cd5e353ffa7fe91f75675b3f540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Thu, 1 Jun 2023 12:40:14 -0300 Subject: [PATCH 04/12] refactor: using the same getAddress API, allowing the user to filter by index --- serverless.yml | 4 +++ src/api/addresses.ts | 79 ++++++++++++++++++++------------------------ tests/api.test.ts | 41 ++++++++++++++++++----- 3 files changed, 72 insertions(+), 52 deletions(-) diff --git a/serverless.yml b/serverless.yml index 550f8cbe..866b8f0c 100644 --- a/serverless.yml +++ b/serverless.yml @@ -314,6 +314,10 @@ functions: method: get cors: true authorizer: ${self:custom.authorizer.walletBearer} + request: + parameters: + paths: + index: false warmup: walletWarmer: enabled: true diff --git a/src/api/addresses.ts b/src/api/addresses.ts index 45da2371..6b884554 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -36,7 +36,7 @@ const checkMineBodySchema = Joi.object({ class AddressAtIndexValidator { static readonly bodySchema = Joi.object({ - index: Joi.number().min(0).required(), + index: Joi.number().min(0).optional(), }); static validate(payload: unknown): { value: AddressAtIndexRequest, error: ValidationError} { @@ -112,7 +112,7 @@ export const checkMine: APIGatewayProxyHandler = middy(walletIdProxyHandler(asyn * * This lambda is called by API Gateway on GET /addresses */ -export const getAddressAtIndex: APIGatewayProxyHandler = middy( +export const get: APIGatewayProxyHandler = middy( walletIdProxyHandler(async (walletId, event) => { const status = await getWallet(mysql, walletId); @@ -135,51 +135,44 @@ export const getAddressAtIndex: APIGatewayProxyHandler = middy( return closeDbAndGetError(mysql, ApiError.INVALID_PAYLOAD, { details }); } - const address: AddressInfo | null = await dbGetAddressAtIndex(mysql, walletId, body.index); - - // If the walletId is valid and the wallet is ready we should have the address in our - // database. If we don't, alert. - if (!address) { - await addAlert( - 'Error on onNewTxRequest', - 'Erroed on onNewTxRequest lambda', - Severity.MINOR, - { walletId, error: `getAddressAtIndex was called with a valid walletId but could not find the address at index ${body.index}` }, - ); + let response = null; + + if (body.index) { + const address: AddressInfo | null = await dbGetAddressAtIndex(mysql, walletId, body.index); + + // If the walletId is valid and the wallet is ready we should have the address in our + // database. If we don't, alert. + if (!address) { + await addAlert( + 'Error on onNewTxRequest', + 'Erroed on onNewTxRequest lambda', + Severity.MINOR, + { walletId, error: `getAddressAtIndex was called with a valid walletId but could not find the address at index ${body.index}` }, + ); + } + + response = { + statusCode: 200, + body: JSON.stringify({ + success: true, + addresses: [address], + }), + }; + } else { + // Searching for multiple addresses + const addresses = await getWalletAddresses(mysql, walletId); + response = { + statusCode: 200, + body: JSON.stringify({ + success: true, + addresses, + }), + }; } await closeDbConnection(mysql); - return { - statusCode: 200, - body: JSON.stringify({ success: true, address }), - }; + return response; }), ).use(cors()) .use(warmupMiddleware()); - -/* - * Get the addresses of a wallet - * - * This lambda is called by API Gateway on GET /addresses - */ -export const get: APIGatewayProxyHandler = middy(walletIdProxyHandler(async (walletId) => { - const status = await getWallet(mysql, walletId); - - if (!status) { - return closeDbAndGetError(mysql, ApiError.WALLET_NOT_FOUND); - } - if (!status.readyAt) { - return closeDbAndGetError(mysql, ApiError.WALLET_NOT_READY); - } - - const addresses = await getWalletAddresses(mysql, walletId); - - await closeDbConnection(mysql); - - return { - statusCode: 200, - body: JSON.stringify({ success: true, addresses }), - }; -})).use(cors()) - .use(warmupMiddleware()); diff --git a/tests/api.test.ts b/tests/api.test.ts index df2ab783..79f351d9 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -132,13 +132,13 @@ test('GET /addresses', async () => { createdAt: 10000, readyAt: 10001, }]); - await addToAddressTable(mysql, [ + + const addresses = [ { address: ADDRESSES[0], index: 0, walletId: 'my-wallet', transactions: 0 }, { address: ADDRESSES[1], index: 1, walletId: 'my-wallet', transactions: 0 }, - ]); + ]; - // TODO: test missing walletId? - // Authorizer should be responsible for this + await addToAddressTable(mysql, addresses); // missing wallet await _testMissingWallet(addressesGet, 'some-wallet'); @@ -149,14 +149,37 @@ test('GET /addresses', async () => { await _testCORSHeaders(addressesGet, 'my-wallet', {}); // success case - const event = makeGatewayEventWithAuthorizer('my-wallet', {}); - const result = await addressesGet(event, null, null) as APIGatewayProxyResult; - const returnBody = JSON.parse(result.body as string); + let event = makeGatewayEventWithAuthorizer('my-wallet', {}); + let result = await addressesGet(event, null, null) as APIGatewayProxyResult; + let returnBody = JSON.parse(result.body as string); expect(result.statusCode).toBe(200); expect(returnBody.success).toBe(true); expect(returnBody.addresses).toHaveLength(2); - expect(returnBody.addresses).toContainEqual({ address: ADDRESSES[0], index: 0, transactions: 0 }); - expect(returnBody.addresses).toContainEqual({ address: ADDRESSES[1], index: 1, transactions: 0 }); + expect(returnBody.addresses).toContainEqual({ + address: addresses[0].address, + index: addresses[0].index, + transactions: addresses[0].transactions, + }); + expect(returnBody.addresses).toContainEqual({ + address: addresses[1].address, + index: addresses[1].index, + transactions: addresses[1].transactions, + }); + + // we should be able to filter for a specific index + event = makeGatewayEventWithAuthorizer('my-wallet', { + index: String(addresses[0].index), + }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + + expect(result.statusCode).toBe(200); + expect(returnBody.success).toBe(true); + expect(returnBody.addresses).toContainEqual({ + address: addresses[0].address, + index: addresses[0].index, + transactions: addresses[0].transactions, + }); }); test('GET /addresses/check_mine', async () => { From a8aeaf441b382aec59444fc94738260664642f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Thu, 1 Jun 2023 13:01:04 -0300 Subject: [PATCH 05/12] refactor: returning 404 on address not found --- src/api/addresses.ts | 9 +-------- src/api/errors.ts | 1 + src/api/utils.ts | 1 + tests/api.test.ts | 11 +++++++++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/api/addresses.ts b/src/api/addresses.ts index 6b884554..b8bfc9a8 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -140,15 +140,8 @@ export const get: APIGatewayProxyHandler = middy( if (body.index) { const address: AddressInfo | null = await dbGetAddressAtIndex(mysql, walletId, body.index); - // If the walletId is valid and the wallet is ready we should have the address in our - // database. If we don't, alert. if (!address) { - await addAlert( - 'Error on onNewTxRequest', - 'Erroed on onNewTxRequest lambda', - Severity.MINOR, - { walletId, error: `getAddressAtIndex was called with a valid walletId but could not find the address at index ${body.index}` }, - ); + return closeDbAndGetError(mysql, ApiError.ADDRESS_NOT_FOUND); } response = { diff --git a/src/api/errors.ts b/src/api/errors.ts index 81a289df..d1d3dc72 100644 --- a/src/api/errors.ts +++ b/src/api/errors.ts @@ -30,6 +30,7 @@ export enum ApiError { WALLET_ALREADY_LOADED = 'wallet-already-loaded', WALLET_MAX_RETRIES = 'wallet-max-retries', ADDRESS_NOT_IN_WALLET = 'address-not-in-wallet', + ADDRESS_NOT_FOUND = 'address-not-found', TX_OUTPUT_NOT_IN_WALLET = 'tx-output-not-in-wallet', TOKEN_NOT_FOUND = 'token-not-found', FORBIDDEN = 'forbidden', diff --git a/src/api/utils.ts b/src/api/utils.ts index 3295d825..76e812a6 100644 --- a/src/api/utils.ts +++ b/src/api/utils.ts @@ -53,6 +53,7 @@ export const STATUS_CODE_TABLE = { [ApiError.TOKEN_NOT_FOUND]: 404, [ApiError.DEVICE_NOT_FOUND]: 404, [ApiError.TX_NOT_FOUND]: 404, + [ApiError.ADDRESS_NOT_FOUND]: 404, }; /** diff --git a/tests/api.test.ts b/tests/api.test.ts index 79f351d9..6cbdd56a 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -180,6 +180,17 @@ test('GET /addresses', async () => { index: addresses[0].index, transactions: addresses[0].transactions, }); + + // we should receive ApiError.ADDRESS_NOT_FOUND if the address was not found + event = makeGatewayEventWithAuthorizer('my-wallet', { + index: '150', + }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + + expect(result.statusCode).toBe(404); + expect(returnBody.success).toBe(false); + expect(returnBody.error).toBe(ApiError.ADDRESS_NOT_FOUND); }); test('GET /addresses/check_mine', async () => { From 79b8d38b5a7f3c82f114191cac70415806cbb9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 09:41:30 -0300 Subject: [PATCH 06/12] refactor: rollback to mysql instead of mysql2 --- src/db/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/index.ts b/src/db/index.ts index 66a66ac5..b73067bf 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -7,7 +7,7 @@ import { strict as assert } from 'assert'; import { ServerlessMysql } from 'serverless-mysql'; import { get } from 'lodash'; -import { OkPacket } from 'mysql2'; +import { OkPacket } from 'mysql'; import { constants } from '@hathor/wallet-lib'; import { AddressIndexMap, From 724fde24511dc475c05a0af2691a49a7a2f18399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 09:47:25 -0300 Subject: [PATCH 07/12] docs: added a comment explaining that we won\'t derive new addresseS --- src/api/addresses.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/addresses.ts b/src/api/addresses.ts index b8bfc9a8..619c79fa 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -108,7 +108,9 @@ export const checkMine: APIGatewayProxyHandler = middy(walletIdProxyHandler(asyn })).use(cors()); /* - * Get the addresses of a wallet + * Get the addresses of a wallet, allowing an index filter + * Notice: If the index filter is passed, it will only find addresses + * that are already in our database, this will not derive new addresses * * This lambda is called by API Gateway on GET /addresses */ From 88e2a0d21e03a8aa62794c1ef23024706008b653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 09:48:05 -0300 Subject: [PATCH 08/12] refactor: removed unused imports --- src/api/addresses.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/addresses.ts b/src/api/addresses.ts index 619c79fa..a1c06564 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -16,10 +16,9 @@ import { getWalletAddresses, getAddressAtIndex as dbGetAddressAtIndex, } from '@src/db'; -import { AddressInfo, AddressAtIndexRequest, Severity } from '@src/types'; +import { AddressInfo, AddressAtIndexRequest } from '@src/types'; import { closeDbConnection, getDbConnection } from '@src/utils'; import { walletIdProxyHandler } from '@src/commons'; -import { addAlert } from '@src/utils/alerting.utils'; import middy from '@middy/core'; import cors from '@middy/http-cors'; From 8b3a064baafd4fcd5dedc94b242b4613aa8c7792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 09:48:41 -0300 Subject: [PATCH 09/12] fix: addressatindexrequest type --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index cf03b687..2b70f4b2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -710,7 +710,7 @@ export interface PushDelete { } export interface AddressAtIndexRequest { - index: number, + index?: number, } export interface TxByIdRequest { From f3c50182de3f3109285a1b2ff2ebf35bfc4be05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 09:55:50 -0300 Subject: [PATCH 10/12] fix: body.index returning false when index was 0 --- src/api/addresses.ts | 2 +- tests/api.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/api/addresses.ts b/src/api/addresses.ts index a1c06564..ce49aedd 100644 --- a/src/api/addresses.ts +++ b/src/api/addresses.ts @@ -138,7 +138,7 @@ export const get: APIGatewayProxyHandler = middy( let response = null; - if (body.index) { + if ('index' in body) { const address: AddressInfo | null = await dbGetAddressAtIndex(mysql, walletId, body.index); if (!address) { diff --git a/tests/api.test.ts b/tests/api.test.ts index 6cbdd56a..585b83bc 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -175,11 +175,12 @@ test('GET /addresses', async () => { expect(result.statusCode).toBe(200); expect(returnBody.success).toBe(true); - expect(returnBody.addresses).toContainEqual({ + expect(returnBody.addresses).toHaveLength(1); + expect(returnBody.addresses).toStrictEqual([{ address: addresses[0].address, index: addresses[0].index, transactions: addresses[0].transactions, - }); + }]); // we should receive ApiError.ADDRESS_NOT_FOUND if the address was not found event = makeGatewayEventWithAuthorizer('my-wallet', { From 5c9ab39842230a66ef94e45dcbed248de953fef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 10:06:51 -0300 Subject: [PATCH 11/12] docs: improved docs on nix flakes --- README.md | 13 +++++++++++++ flake.nix | 19 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21fff143..1c4c59fa 100644 --- a/README.md +++ b/README.md @@ -207,3 +207,16 @@ Sometimes, jest will use old cached js files, even after you modified the typesc ## Standard Operating Procedures Check it in [docs/SOP.md](docs/SOP.md) + + +## Nix flakes + +## Using this project + +This project uses [Nix](https://nixos.org/) with [direnv](https://direnv.net/) to help with dependencies, including Node.js. To get started, you need to have Nix and direnv installed. + +1. Install [Nix](https://nixos.org/download.html) and [Direnv](https://direnv.net/docs/installation.html). +2. Enable flake support in Nix: `nix-env -iA nixpkgs.nixUnstable` +3. Allow direnv to work in your shell by running `direnv allow` + +Now, every time you enter the project directory, direnv will automatically activate the environment from flake.nix, including the specific version of Node.js specified there. When you leave the directory, it will deactivate. This ensures a consistent and isolated environment per project. diff --git a/flake.nix b/flake.nix index 9d2f2498..4669fa5e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,21 @@ +/* +This flake.nix file creates a virtual environment with the desired dependencies +in a reproducible way using Nix and Nix flakes (https://nixos.wiki/wiki/Flakes) + +Flakes are a feature in Nix that allows you to specify the dependencies of your +project in a declarative and reproducible manner. It allows for better isolation, +reproducibility, and more reliable upgrades. + +`direnv` is an environment switcher for the shell. It knows how to hook into +multiple shells (like bash, zsh, fish, etc...) to load or unload environment +variables depending on the current directory. This allows project-specific +environment variables without cluttering the "~/.profile" file. + +This flake file creates a shell with nodejs v14.x installed and should work +on macOs, linux and windows +*/ { - description = "virtual environments"; + description = "Flake that installs Node.js 14.x via direnv"; inputs.devshell.url = "github:numtide/devshell"; inputs.flake-utils.url = "github:numtide/flake-utils"; @@ -16,7 +32,6 @@ in pkgs.devshell.mkShell { packages = with pkgs; [ - nixpkgs-fmt nodejs-14_x ]; }; From a3a71a435d5d5fe05597e6a3a8087f9d2ab76701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abadesso?= Date: Mon, 5 Jun 2023 11:04:04 -0300 Subject: [PATCH 12/12] tests: added a test for invalid payload --- tests/api.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/api.test.ts b/tests/api.test.ts index 585b83bc..12c7418f 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -28,6 +28,7 @@ import * as Wallet from '@src/api/wallet'; import * as Db from '@src/db'; import { ApiError } from '@src/api/errors'; import { closeDbConnection, getDbConnection, getUnixTimestamp, getWalletId } from '@src/utils'; +import { STATUS_CODE_TABLE } from '@src/api/utils'; import { WalletStatus, FullNodeVersionData } from '@src/types'; import { walletUtils, constants, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; import bitcore from 'bitcore-lib'; @@ -166,6 +167,18 @@ test('GET /addresses', async () => { transactions: addresses[1].transactions, }); + // we should error on invalid index parameter + event = makeGatewayEventWithAuthorizer('my-wallet', { + index: '-50', + }); + result = await addressesGet(event, null, null) as APIGatewayProxyResult; + returnBody = JSON.parse(result.body as string); + + expect(result.statusCode).toBe(STATUS_CODE_TABLE[ApiError.INVALID_PAYLOAD]); + expect(returnBody.details).toHaveLength(1); + expect(returnBody.details[0].message) + .toMatchInlineSnapshot('"\\"index\\" must be greater than or equal to 0"'); + // we should be able to filter for a specific index event = makeGatewayEventWithAuthorizer('my-wallet', { index: String(addresses[0].index),