From 96cda976357c98242aca1d660115c8d622f7b796 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Thu, 30 Jul 2026 02:36:32 -0700 Subject: [PATCH] perf(deposits): derive the HD master key once, not once per user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-deposits hits the isolate's CPU ceiling on almost every run — 17 limit events across 20 invocations in the last 20 minutes — and when it does, the supervisor cancels the worker and the scan stops partway. Deposits after the cut-off simply are not seen until some later run happens to reach them. The cause is in deriveTronAddress: it calls mnemonicToSeedSync per user, which is PBKDF2 with 2048 iterations. The seed depends only on the mnemonic, so with 51 accounts that cost was paid 51 times a minute to produce the identical seed. Only the derivation path varies per user. Derive the master key once and reuse it. Measured over the 51 accounts we actually scan: 418ms -> 58ms, a 7.2x drop in the part that was blowing the budget. Verified the addresses are unchanged: old and new paths produce identical private keys for every index tested, including repeated indexes in one run, which is what would expose derive() mutating the parent key. It does not. --- supabase/functions/check-deposits/index.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/supabase/functions/check-deposits/index.ts b/supabase/functions/check-deposits/index.ts index 175c3ee..214edef 100644 --- a/supabase/functions/check-deposits/index.ts +++ b/supabase/functions/check-deposits/index.ts @@ -67,9 +67,23 @@ function base58CheckEncode(payload: Uint8Array): string { } // Derive TRC20 address from HD wallet +// mnemonicToSeedSync is PBKDF2 with 2048 iterations and the seed depends only +// on the mnemonic, so deriving it per user burned that cost once per account on +// every run. With ~50 accounts and a one-minute schedule that was enough to hit +// the isolate's CPU ceiling on nearly every invocation, cutting the scan short. +// Derive the master key once and reuse it; only the path varies per user. +let cachedMasterKey: HDKey | null = null; + +function getMasterKey(mnemonic: string): HDKey { + if (!cachedMasterKey) { + const seed = bip39.mnemonicToSeedSync(mnemonic, ''); + cachedMasterKey = HDKey.fromMasterSeed(seed); + } + return cachedMasterKey; +} + function deriveTronAddress(mnemonic: string, index: number): string { - const seed = bip39.mnemonicToSeedSync(mnemonic, ''); - const hdKey = HDKey.fromMasterSeed(seed); + const hdKey = getMasterKey(mnemonic); const derivedKey = hdKey.derive(`m/44'/195'/0'/0/${index}`); if (!derivedKey.privateKey) throw new Error('Failed to derive private key');