perf(deposits): derive the HD master key once, not once per user

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.
This commit is contained in:
2026-07-30 02:36:32 -07:00
parent e97d73a0dd
commit 96cda97635
+16 -2
View File
@@ -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');