From f94fdc6121b700dfe2d0e6b36c16d98bb1e4ab9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Mon, 8 Sep 2025 13:40:50 -0300 Subject: [PATCH 1/8] chore: warmup middleware for authorization lambdas --- packages/wallet-service/serverless.yml | 4 ++-- packages/wallet-service/src/api/auth.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/wallet-service/serverless.yml b/packages/wallet-service/serverless.yml index 85832eab..67ca1eea 100644 --- a/packages/wallet-service/serverless.yml +++ b/packages/wallet-service/serverless.yml @@ -588,12 +588,12 @@ functions: cors: true warmup: walletWarmer: - enabled: false + enabled: true bearerAuthorizer: handler: src/api/auth.bearerAuthorizer warmup: walletWarmer: - enabled: false + enabled: true metrics: handler: src/metrics.getMetrics events: diff --git a/packages/wallet-service/src/api/auth.ts b/packages/wallet-service/src/api/auth.ts index 48a6946d..d705b038 100644 --- a/packages/wallet-service/src/api/auth.ts +++ b/packages/wallet-service/src/api/auth.ts @@ -26,6 +26,7 @@ import { validateAuthTimestamp, AUTH_MAX_TIMESTAMP_SHIFT_IN_SECONDS, } from '@src/utils'; +import { warmupMiddleware } from '@src/api/utils'; import middy from '@middy/core'; import cors from '@middy/http-cors'; import createDefaultLogger from '@src/logger'; @@ -155,6 +156,7 @@ export const tokenHandler: APIGatewayProxyHandler = middy(async (event) => { body: JSON.stringify({ success: true, token }), }; }).use(cors()) + .use(warmupMiddleware()) .use(errorHandler()); /** @@ -235,4 +237,5 @@ export const bearerAuthorizer: APIGatewayTokenAuthorizerHandler = middy(async (e } return _generatePolicy(walletId, 'Deny', event.methodArn, logger); -}).use(cors()); +}).use(cors()) + .use(warmupMiddleware()); From 3ebd53f15ff601b7f91fce7b6f5ef045d099f11c Mon Sep 17 00:00:00 2001 From: Andre Carneiro Date: Mon, 8 Sep 2025 16:36:14 -0300 Subject: [PATCH 2/8] chore: handle wallet-not-found for auth token api --- packages/wallet-service/src/api/auth.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/wallet-service/src/api/auth.ts b/packages/wallet-service/src/api/auth.ts index d705b038..39c8e535 100644 --- a/packages/wallet-service/src/api/auth.ts +++ b/packages/wallet-service/src/api/auth.ts @@ -84,6 +84,17 @@ export const tokenHandler: APIGatewayProxyHandler = middy(async (event) => { const authXpubStr = value.xpub; const wallet: Wallet = await getWallet(mysql, value.walletId); + if (!wallet) { + await closeDbConnection(mysql); + return { + statusCode: 400, + body: JSON.stringify({ + success: false, + error: ApiError.WALLET_NOT_FOUND, + }), + }; + } + const [validTimestamp, timestampShift] = validateAuthTimestamp(timestamp, Date.now() / 1000); if (!validTimestamp) { From c4cee52ac7214ea3485a585e297234b0e92bcdd3 Mon Sep 17 00:00:00 2001 From: Andre Carneiro Date: Mon, 8 Sep 2025 21:03:08 -0300 Subject: [PATCH 3/8] chore: add optional field on event --- packages/daemon/src/types/event.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/daemon/src/types/event.ts b/packages/daemon/src/types/event.ts index 44c595c2..49c9c5b6 100644 --- a/packages/daemon/src/types/event.ts +++ b/packages/daemon/src/types/event.ts @@ -103,7 +103,8 @@ export const EventTxOutputSchema = z.object({ type: z.string(), address: z.string(), timelock: z.number().nullable(), - }).nullable(), + token_data: z.number().nullish(), + }).passthrough().nullable(), z.object({}).strict() ]), }); From 9be50a0f6e9d78a35de0860cec8d0616a5b6109d Mon Sep 17 00:00:00 2001 From: Andre Carneiro Date: Mon, 8 Sep 2025 23:12:27 -0300 Subject: [PATCH 4/8] chore: add decoded alternative --- packages/daemon/src/types/event.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/daemon/src/types/event.ts b/packages/daemon/src/types/event.ts index 49c9c5b6..035d6f4b 100644 --- a/packages/daemon/src/types/event.ts +++ b/packages/daemon/src/types/event.ts @@ -103,9 +103,11 @@ export const EventTxOutputSchema = z.object({ type: z.string(), address: z.string(), timelock: z.number().nullable(), - token_data: z.number().nullish(), }).passthrough().nullable(), - z.object({}).strict() + z.object({}).strict(), + z.object({ + token_data: z.number().nullish(), + }).strict() ]), }); export type EventTxOutput = z.infer; From 5e2c38c841c4fd60b20959c6cfe411cb69aef51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 9 Sep 2025 14:36:56 -0300 Subject: [PATCH 5/8] chore: do not query for the wallet a second time --- .../wallet-service/src/api/newAddresses.ts | 8 ++-- packages/wallet-service/src/db/index.ts | 43 ++++++++----------- packages/wallet-service/src/db/utils.ts | 1 + packages/wallet-service/src/types.ts | 1 + 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/wallet-service/src/api/newAddresses.ts b/packages/wallet-service/src/api/newAddresses.ts index 3158ff9a..2df96664 100644 --- a/packages/wallet-service/src/api/newAddresses.ts +++ b/packages/wallet-service/src/api/newAddresses.ts @@ -30,16 +30,16 @@ const mysql = getDbConnection(); * This lambda is called by API Gateway on GET /addresses/new */ export const get: APIGatewayProxyHandler = middy(walletIdProxyHandler(async (walletId) => { - const status = await getWallet(mysql, walletId); + const wallet = await getWallet(mysql, walletId); - if (!status) { + if (!wallet) { return closeDbAndGetError(mysql, ApiError.WALLET_NOT_FOUND); } - if (!status.readyAt) { + if (!wallet.readyAt) { return closeDbAndGetError(mysql, ApiError.WALLET_NOT_READY); } - const addresses = await getNewAddresses(mysql, walletId); + const addresses = await getNewAddresses(mysql, wallet); await closeDbConnection(mysql); diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index 7b2959a5..ba7c708d 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -1322,32 +1322,27 @@ export const getWalletAddresses = async (mysql: ServerlessMysql, walletId: strin * @param walletId - Wallet id * @returns A list of addresses and their indexes */ -export const getNewAddresses = async (mysql: ServerlessMysql, walletId: string): Promise => { +export const getNewAddresses = async (mysql: ServerlessMysql, wallet: Wallet): Promise => { const addresses: ShortAddressInfo[] = []; - const resultsWallet: DbSelectResult = await mysql.query('SELECT * FROM `wallet` WHERE `id` = ?', walletId); - if (resultsWallet.length) { - const gapLimit = resultsWallet[0].max_gap as number; - const latestUsedIndex = resultsWallet[0].last_used_address_index as number; - // Select all addresses that are empty and the index is bigger than the last used address index - const results: DbSelectResult = await mysql.query(` - SELECT * - FROM \`address\` - WHERE \`wallet_id\` = ? - AND \`transactions\` = 0 - AND \`index\` > ? - ORDER BY \`index\` - ASC - LIMIT ?`, [walletId, latestUsedIndex, gapLimit]); + // Select all addresses that are empty and the index is bigger than the last used address index + const results: DbSelectResult = await mysql.query(` + SELECT * + FROM \`address\` + WHERE \`wallet_id\` = ? + AND \`transactions\` = 0 + AND \`index\` > ? + ORDER BY \`index\` + ASC + LIMIT ?`, [wallet.walletId, wallet.lastUsedAddressIndex, wallet.maxGap]); - for (const result of results) { - const index = result.index as number; - const address = { - address: result.address as string, - index, - addressPath: getAddressPath(index), - }; - addresses.push(address); - } + for (const result of results) { + const index = result.index as number; + const address = { + address: result.address as string, + index, + addressPath: getAddressPath(index), + }; + addresses.push(address); } return addresses; }; diff --git a/packages/wallet-service/src/db/utils.ts b/packages/wallet-service/src/db/utils.ts index fae728d5..5cd988eb 100644 --- a/packages/wallet-service/src/db/utils.ts +++ b/packages/wallet-service/src/db/utils.ts @@ -83,6 +83,7 @@ export const getWalletFromDbEntry = (entry: Record): Wallet => maxGap: entry.max_gap as number, createdAt: entry.created_at as number, readyAt: entry.ready_at as number, + lastUsedAddressIndex: entry.last_used_address_index as number, }); /** diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index 1ae4f7e2..5f9a4632 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -144,6 +144,7 @@ export interface Wallet { retryCount?: number; createdAt?: number; readyAt?: number; + lastUsedAddressIndex?: number; } export interface AddressInfo { From 6876fdc4fd0a8f5f41127d216f52fe822c3bc3bb Mon Sep 17 00:00:00 2001 From: Andre Carneiro Date: Wed, 10 Sep 2025 13:45:58 -0300 Subject: [PATCH 6/8] chore: event decoded object options --- packages/daemon/src/types/event.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/daemon/src/types/event.ts b/packages/daemon/src/types/event.ts index 035d6f4b..c2ade5ed 100644 --- a/packages/daemon/src/types/event.ts +++ b/packages/daemon/src/types/event.ts @@ -104,10 +104,10 @@ export const EventTxOutputSchema = z.object({ address: z.string(), timelock: z.number().nullable(), }).passthrough().nullable(), - z.object({}).strict(), z.object({ - token_data: z.number().nullish(), - }).strict() + token_data: z.number(), + }), + z.object({}).strict(), ]), }); export type EventTxOutput = z.infer; From fdf6f1442635efe196a3ef4c898494b20a66eb51 Mon Sep 17 00:00:00 2001 From: Andre Carneiro Date: Wed, 10 Sep 2025 15:26:34 -0300 Subject: [PATCH 7/8] tests: fix new addresses tests --- packages/wallet-service/src/db/index.ts | 2 ++ packages/wallet-service/src/db/utils.ts | 2 +- packages/wallet-service/tests/api.test.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index ba7c708d..a26da124 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -289,6 +289,7 @@ export const createWallet = async ( status: WalletStatus.CREATING, created_at: ts, max_gap: maxGap, + last_used_address_index: -1, }; await mysql.query( `INSERT INTO \`wallet\` @@ -304,6 +305,7 @@ export const createWallet = async ( status: WalletStatus.CREATING, createdAt: ts, readyAt: null, + lastUsedAddressIndex: -1, }; }; diff --git a/packages/wallet-service/src/db/utils.ts b/packages/wallet-service/src/db/utils.ts index 5cd988eb..e66f6011 100644 --- a/packages/wallet-service/src/db/utils.ts +++ b/packages/wallet-service/src/db/utils.ts @@ -75,7 +75,7 @@ export async function transactionDecorator(_mysql: ServerlessMysql, wrapped: Fun * @param result - The result row to map to a Wallet object */ export const getWalletFromDbEntry = (entry: Record): Wallet => ({ - walletId: getWalletId(entry.xpubkey as string), + walletId: entry.id as string, xpubkey: entry.xpubkey as string, authXpubkey: entry.auth_xpubkey as string, status: entry.status as WalletStatus, diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index cb338c94..965bb0f7 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -750,7 +750,7 @@ test('GET /wallet', async () => { expect(result.statusCode).toBe(200); expect(returnBody.success).toBe(true); expect(returnBody.status).toStrictEqual({ - walletId: getWalletId(XPUBKEY), + walletId: 'my-wallet', xpubkey: XPUBKEY, authXpubkey: AUTH_XPUBKEY, status: 'ready', @@ -758,6 +758,7 @@ test('GET /wallet', async () => { retryCount: 0, createdAt: 10000, readyAt: 10001, + lastUsedAddressIndex: -1, }); }); From d8b9e038684f4bb8c034e300d5baad9bcdadcdd3 Mon Sep 17 00:00:00 2001 From: Andre Carneiro Date: Wed, 10 Sep 2025 16:48:05 -0300 Subject: [PATCH 8/8] chore: update last event id --- packages/daemon/__tests__/integration/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/daemon/__tests__/integration/config.ts b/packages/daemon/__tests__/integration/config.ts index 5eed2bf7..ab84fa97 100644 --- a/packages/daemon/__tests__/integration/config.ts +++ b/packages/daemon/__tests__/integration/config.ts @@ -11,7 +11,7 @@ export const UNVOIDED_SCENARIO_PORT = 8081; // Last event is actually 39, but event 39 is ignored by the machine (because // the transaction is already added), and when we ignore an event, we don't store // it in the database. -export const UNVOIDED_SCENARIO_LAST_EVENT = 38; +export const UNVOIDED_SCENARIO_LAST_EVENT = 39; // reorg export const REORG_SCENARIO_PORT = 8082;