From 6d580b2ae2870a202970b51f2cd78d3f74b2551e Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sat, 16 May 2026 00:27:58 -0700 Subject: [PATCH] Extract frontier block caching. --- src/lib/ledger/cache.ts | 53 ++++++++++++++++++++++++++++++++++++++ src/lib/ledger/index.ts | 56 +++-------------------------------------- 2 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 src/lib/ledger/cache.ts diff --git a/src/lib/ledger/cache.ts b/src/lib/ledger/cache.ts new file mode 100644 index 0000000..145556e --- /dev/null +++ b/src/lib/ledger/cache.ts @@ -0,0 +1,53 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { APDU_CODES, LedgerResponse, LedgerTransport, STATUS_CODES, listenTimeout, openTimeout } from '.' +import { Account } from '../account' +import { Block } from '../block' +import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from '../constants' +import { bytes, dec, hex } from '../convert' +import { queue } from './queue' + +export async function _cache (transport: LedgerTransport, index: number = 0, block: Block): Promise { + return queue(async () => { + try { + if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { + throw new TypeError('Invalid account index') + } + if (!(block instanceof Block)) { + throw new TypeError('Invalid block format') + } + if (!(block.link instanceof Uint8Array)) { + throw new TypeError('Invalid block link') + } + if (!(block.representative instanceof Account)) { + throw new TypeError('Invalid block link') + } + if (!block.signature) { + throw new ReferenceError('Cannot cache unsigned block') + } + + const purpose = dec.toBytes(BIP44_PURPOSE + HARDENED_OFFSET, 4) + const coin = dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4) + const account = dec.toBytes(index + HARDENED_OFFSET, 4) + const previous = block.previous + const link = block.link + const representative = hex.toBytes(block.representative.publicKey, 32) + const balance = hex.toBytes(block.balance.toString(16), 16) + const signature = hex.toBytes(block.signature, 64) + const data = new Uint8Array([APDU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature]) + + const t = await transport.create(openTimeout, listenTimeout) + const response = await t + .send(APDU_CODES.class, APDU_CODES.cacheBlock, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data as Buffer) + .then((res: Buffer) => bytes.toDec(res)) + .catch((err: any) => err.statusCode) + .finally(async () => await t.close()) as number + + return { status: STATUS_CODES[response] } + } catch (err: any) { + console.error('Ledger.#cacheBlock()', err) + return { status: err.message } + } + }) +} diff --git a/src/lib/ledger/index.ts b/src/lib/ledger/index.ts index ecf70f4..aa1a1bd 100644 --- a/src/lib/ledger/index.ts +++ b/src/lib/ledger/index.ts @@ -12,6 +12,7 @@ import { bytes, dec, hex, utf8 } from '../convert' import { Rpc } from '../rpc' import { Wallet } from '../wallet' import { _account } from './account' +import { _cache } from './cache' import { _connect } from './connect' import { queue } from './queue' @@ -230,7 +231,7 @@ export class Ledger { throw new TypeError('Data to be signed must be a string nonce or a Block', { cause: data }) } if (frontier != null) { - const { status } = await this.#cacheBlock(index, frontier) + const { status } = await _cache(this.#transport, index, frontier) if (status !== 'OK') { throw new Error('Failed to cache frontier block in ledger', { cause: status }) } @@ -283,7 +284,7 @@ export class Ledger { } input = res.contents } - const { status } = await this.#cacheBlock(index, input) + const { status } = await _cache(this.#transport, index, input) if (status !== 'OK') { throw new Error('failed to cache frontier block in ledger', { cause: status }) } @@ -325,57 +326,6 @@ export class Ledger { } } - /** - * Cache frontier block in device memory. - * - * @param {number} index - Account number - * @param {any} block - Block data to cache - * @returns Status of command - */ - static async #cacheBlock (index: number = 0, block: Block): Promise { - return queue(async () => { - try { - if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { - throw new TypeError('Invalid account index') - } - if (!(block instanceof Block)) { - throw new TypeError('Invalid block format') - } - if (!(block.link instanceof Uint8Array)) { - throw new TypeError('Invalid block link') - } - if (!(block.representative instanceof Account)) { - throw new TypeError('Invalid block link') - } - if (!block.signature) { - throw new ReferenceError('Cannot cache unsigned block') - } - - const purpose = dec.toBytes(BIP44_PURPOSE + HARDENED_OFFSET, 4) - const coin = dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4) - const account = dec.toBytes(index + HARDENED_OFFSET, 4) - const previous = block.previous - const link = block.link - const representative = hex.toBytes(block.representative.publicKey, 32) - const balance = hex.toBytes(block.balance.toString(16), 16) - const signature = hex.toBytes(block.signature, 64) - const data = new Uint8Array([APDU_CODES.bip32DerivationLevel, ...purpose, ...coin, ...account, ...previous, ...link, ...representative, ...balance, ...signature]) - - const transport = await this.#transport.create(openTimeout, listenTimeout) - const response = await transport - .send(APDU_CODES.class, APDU_CODES.cacheBlock, APDU_CODES.paramUnused, APDU_CODES.paramUnused, data as Buffer) - .then((res: Buffer) => bytes.toDec(res)) - .catch((err: any) => err.statusCode) - .finally(async () => await transport.close()) as number - - return { status: STATUS_CODES[response] } - } catch (err: any) { - console.error('Ledger.#cacheBlock()', err) - return { status: err.message } - } - }) - } - /** * Close the currently running app and return to the device dashboard. * -- 2.47.3