From 154c0e0a4a077a684f40347fe7223a748f424c48 Mon Sep 17 00:00:00 2001 From: k00b Date: Sun, 6 Oct 2024 17:58:46 -0500 Subject: [PATCH] fix cache key bloat --- api/lnd/index.js | 26 ++++++++++++++++---------- api/resolvers/blockHeight.js | 3 ++- api/resolvers/chainFee.js | 3 ++- api/resolvers/price.js | 6 +++--- api/resolvers/wallet.js | 12 ++++++------ lib/fetch.js | 4 ++-- wallets/wrap.js | 2 +- 7 files changed, 32 insertions(+), 24 deletions(-) diff --git a/api/lnd/index.js b/api/lnd/index.js index 035afcf9..9ad6eca3 100644 --- a/api/lnd/index.js +++ b/api/lnd/index.js @@ -108,9 +108,9 @@ export function getPaymentFailureStatus (withdrawal) { } } -export const getBlockHeight = cachedFetcher(async () => { +export const getBlockHeight = cachedFetcher(async ({ lnd, ...args }) => { try { - const { current_block_height: height } = await getHeight({ lnd }) + const { current_block_height: height } = await getHeight({ lnd, ...args }) return height } catch (err) { throw new Error(`Unable to fetch block height: ${err.message}`) @@ -118,12 +118,13 @@ export const getBlockHeight = cachedFetcher(async () => { }, { maxSize: 1, cacheExpiry: 60 * 1000, // 1 minute - forceRefreshThreshold: 5 * 60 * 1000 // 5 minutes + forceRefreshThreshold: 5 * 60 * 1000, // 5 minutes + keyGenerator: () => 'getHeight' }) -export const getOurPubkey = cachedFetcher(async () => { +export const getOurPubkey = cachedFetcher(async ({ lnd, ...args }) => { try { - const { identity } = await getIdentity({ lnd }) + const { identity } = await getIdentity({ lnd, ...args }) return identity.public_key } catch (err) { throw new Error(`Unable to fetch identity: ${err.message}`) @@ -131,19 +132,24 @@ export const getOurPubkey = cachedFetcher(async () => { }, { maxSize: 1, cacheExpiry: 0, // never expire - forceRefreshThreshold: 0 // never force refresh + forceRefreshThreshold: 0, // never force refresh + keyGenerator: () => 'getOurPubkey' }) -export const getNodeInfo = cachedFetcher(async (args) => { +export const getNodeSockets = cachedFetcher(async ({ lnd, ...args }) => { try { - return await getNode({ lnd, ...args }) + return (await getNode({ lnd, is_omitting_channels: true, ...args }))?.sockets } catch (err) { throw new Error(`Unable to fetch node info: ${err.message}`) } }, { - maxSize: 1000, + maxSize: 100, cacheExpiry: 1000 * 60 * 60 * 24, // 1 day - forceRefreshThreshold: 1000 * 60 * 60 * 24 * 7 // 1 week + forceRefreshThreshold: 1000 * 60 * 60 * 24 * 7, // 1 week + keyGenerator: (args) => { + const { public_key: publicKey } = args + return publicKey + } }) export default lnd diff --git a/api/resolvers/blockHeight.js b/api/resolvers/blockHeight.js index 5739b634..2a1a89c1 100644 --- a/api/resolvers/blockHeight.js +++ b/api/resolvers/blockHeight.js @@ -13,7 +13,8 @@ const getBlockHeight = cachedFetcher(async ({ lnd }) => { }, { maxSize: 1, cacheExpiry: 60 * 1000, // 1 minute - forceRefreshThreshold: 0 + forceRefreshThreshold: 0, + keyGenerator: () => 'getBlockHeight' }) export default { diff --git a/api/resolvers/chainFee.js b/api/resolvers/chainFee.js index d66e7a09..919ffd5a 100644 --- a/api/resolvers/chainFee.js +++ b/api/resolvers/chainFee.js @@ -13,7 +13,8 @@ const getChainFeeRate = cachedFetcher(async () => { }, { maxSize: 1, cacheExpiry: 60 * 1000, // 1 minute - forceRefreshThreshold: 0 // never force refresh + forceRefreshThreshold: 0, // never force refresh + keyGenerator: () => 'getChainFeeRate' }) export default { diff --git a/api/resolvers/price.js b/api/resolvers/price.js index d261d137..38c4d7dd 100644 --- a/api/resolvers/price.js +++ b/api/resolvers/price.js @@ -1,8 +1,7 @@ import { SUPPORTED_CURRENCIES } from '@/lib/currency' import { cachedFetcher } from '@/lib/fetch' -const getPrice = cachedFetcher(async (fiat) => { - fiat ??= 'USD' +const getPrice = cachedFetcher(async (fiat = 'USD') => { const url = `https://api.coinbase.com/v2/prices/BTC-${fiat}/spot` try { const res = await fetch(url) @@ -15,7 +14,8 @@ const getPrice = cachedFetcher(async (fiat) => { }, { maxSize: SUPPORTED_CURRENCIES.length, cacheExpiry: 60 * 1000, // 1 minute - forceRefreshThreshold: 0 // never force refresh + forceRefreshThreshold: 0, // never force refresh + keyGenerator: (fiat = 'USD') => fiat }) export default { diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index d2f29a07..03945fec 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -22,7 +22,7 @@ import walletDefs from 'wallets/server' import { generateResolverName, generateTypeDefName } from '@/lib/wallet' import { lnAddrOptions } from '@/lib/lnurl' import { GqlAuthenticationError, GqlAuthorizationError, GqlInputError } from '@/lib/error' -import { getNodeInfo, getOurPubkey } from '../lnd' +import { getNodeSockets, getOurPubkey } from '../lnd' function injectResolvers (resolvers) { console.group('injected GraphQL resolvers:') @@ -723,7 +723,7 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model invoice = invoice.replace(/^lightning:/, '') // decode invoice to get amount - let decoded, node + let decoded, sockets try { decoded = await parsePaymentRequest({ request: invoice }) } catch (error) { @@ -732,14 +732,14 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model } try { - node = await getNodeInfo({ public_key: decoded.destination, is_omitting_channels: true }) + sockets = await getNodeSockets({ lnd, public_key: decoded.destination }) } catch (error) { // likely not found if it's an unannounced channel, e.g. phoenix console.log(error) } - if (node) { - for (const { socket } of node.sockets) { + if (sockets) { + for (const { socket } of sockets) { const ip = socket.split(':')[0] await assertGofacYourself({ models, headers, ip }) } @@ -835,7 +835,7 @@ export async function fetchLnAddrInvoice ( // decode invoice try { const decoded = await parsePaymentRequest({ request: res.pr }) - const ourPubkey = await getOurPubkey() + const ourPubkey = await getOurPubkey({ lnd }) if (autoWithdraw && decoded.destination === ourPubkey && process.env.NODE_ENV === 'production') { // unset lnaddr so we don't trigger another withdrawal with same destination await models.wallet.deleteMany({ diff --git a/lib/fetch.js b/lib/fetch.js index 88ede993..c4267df5 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -36,11 +36,11 @@ class LRUCache { } } -export function cachedFetcher (fetcher, { maxSize = 100, cacheExpiry, forceRefreshThreshold }) { +export function cachedFetcher (fetcher, { maxSize = 100, cacheExpiry, forceRefreshThreshold, keyGenerator }) { const cache = new LRUCache(maxSize) return async function cachedFetch (...args) { - const key = JSON.stringify(args) + const key = keyGenerator ? keyGenerator(...args) : JSON.stringify(args) const now = Date.now() async function fetchAndCache () { diff --git a/wallets/wrap.js b/wallets/wrap.js index 91b7eafc..b9e22e5c 100644 --- a/wallets/wrap.js +++ b/wallets/wrap.js @@ -131,7 +131,7 @@ export default async function wrapInvoice (bolt11, { msats, description, descrip timeout: FEE_ESTIMATE_TIMEOUT_SECS }) - const blockHeight = await getBlockHeight() + const blockHeight = await getBlockHeight({ lnd }) /* we want the incoming invoice to have MIN_SETTLEMENT_CLTV_DELTA higher final cltv delta than the expected ctlv_delta of the outgoing invoice's entire route