From: Chris Duncan Date: Wed, 20 Aug 2025 15:58:03 +0000 (-0700) Subject: Fix refresh from account and from wallet. X-Git-Tag: v0.10.5~41^2~43 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=eb12dd727f58e1ab522cbe63cb4caae02d306dc8;p=libnemo.git Fix refresh from account and from wallet. --- diff --git a/src/lib/account.ts b/src/lib/account.ts index f7e1872..cfb7d08 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -232,7 +232,7 @@ export class Account { this.confirmed_receivable = confirmed_receivable this.balance = balance this.block_height = block_count - this.frontier_block = frontier + this.frontier = frontier this.open_block = open_block this.receivable = receivable this.representative = representative @@ -240,25 +240,33 @@ export class Account { this.weight = weight const reqConfirmedFrontier = { + json_block: true, hash: confirmed_frontier } - const resConfirmedFrontier = await rpc.call('blocks_info', reqConfirmedFrontier) - let contents = resConfirmedFrontier.contents - this.confirmed_frontier_block = new Block(contents.account, contents.balance, contents.previous, contents.representative) - .sign(contents.signature) + const { contents: confirmedFrontierContents, subtype: confirmedFrontierSubtype } = await rpc.call('block_info', reqConfirmedFrontier) + const confirmedFrontierBlock = new Block(confirmedFrontierContents.account, confirmedFrontierContents.balance, confirmedFrontierContents.previous, confirmedFrontierContents.representative) + if (typeof confirmedFrontierBlock[confirmedFrontierSubtype] !== 'function') { + throw new TypeError('Unknown subtype of confirmed frontier block', { cause: confirmedFrontierSubtype }) + } + confirmedFrontierBlock[confirmedFrontierSubtype](confirmedFrontierContents.link, 0).sign(confirmedFrontierContents.signature) + this.confirmed_frontier_block = confirmedFrontierBlock const reqFrontier = { - hash: frontier + json_block: true, + hash: confirmed_frontier + } + const { contents: frontierContents, subtype: frontierSubtype } = await rpc.call('block_info', reqFrontier) + const frontierBlock = new Block(frontierContents.account, frontierContents.balance, frontierContents.previous, frontierContents.representative) + if (typeof frontierBlock[frontierSubtype] !== 'function') { + throw new TypeError('Unknown subtype of frontier block', { cause: frontierSubtype }) } - const resFrontier = await rpc.call('blocks_info', reqFrontier) - contents = resFrontier.contents - this.frontier_block = new Block(contents.account, contents.balance, contents.previous, contents.representative) - .sign(contents.signature) + frontierBlock[frontierSubtype](frontierContents.link, 0).sign(frontierContents.signature) + this.frontier_block = frontierBlock } /** * Validates a Nano address with 'nano' and 'xrb' prefixes - * Derived from https://github.com/alecrios/nano-address-validator + * rived from https://github.com/alecrios/nano-address-validator * * @param {string} address - Nano address to validate * @throws Error if address is undefined, not a string, or an invalid format diff --git a/src/lib/block.ts b/src/lib/block.ts index a9d04b3..16bc88b 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -14,6 +14,7 @@ import { convert } from './tools' * Represents a block as defined by the Nano cryptocurrency protocol. */ export class Block { + [key: string]: bigint | string | Account | Function | Uint8Array | 'send' | 'receive' | 'change' | undefined /** * Validates block data. * @@ -101,7 +102,7 @@ export class Block { throw new TypeError('Invalid account') } balance ??= account.balance - previous ??= account.frontier_block + previous ??= account.frontier representative ??= account.representative if (typeof balance !== 'bigint' && typeof balance !== 'number' && typeof balance !== 'string') { throw new TypeError('Account balance is unknown') @@ -360,7 +361,7 @@ export class Block { } if (typeof account !== 'string' && !(account instanceof Account)) { - throw new TypeError('Invalid account') + throw new TypeError('Invalid account', { cause: account }) } this.link = (typeof account === 'string') ? hex.toBytes(Account.load(account).publicKey) diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index 8af6bb0..d4c5ed6 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -262,8 +262,8 @@ export class Wallet { * * A successful response will set these properties on each account. * - * @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks - * @returns {Promise} Accounts with updated balances, frontiers, and representatives + * @param {(Rpc|string|URL)} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks + * @returns {Promise} Promise for accounts with updated balances, frontiers, and representatives */ async refresh (rpc: Rpc | string | URL, from: number = 0, to: number = from): Promise { return await _refresh(this, rpc, from, to) @@ -275,10 +275,21 @@ export class Wallet { * before being returned. The wallet must be unlocked prior to signing. * * @param {number} index - Account to use for signing - * @param {(Block)} block - Block data to be hashed and signed + * @param {Block} block - Block data to be hashed and signed + */ + async sign (index: number, block: Block): Promise + /** + * Signs a block using a Ledger hardware wallet at the wallet index specified. + * The signature is appended to the signature field of the block before being + * returned. The wallet must be unlocked prior to signing. + * + * @param {number} index - Account to use for signing + * @param {Block} block - Block data to be hashed and signed + * @param {Block} frontier - Previous block data to be cached by Ledger wallet */ - async sign (index: number, block: Block): Promise { - await _sign(this, this.#vault, index, block) + async sign (index: number, block: Block, frontier: Block): Promise + async sign (index: number, block: Block, frontier?: Block): Promise { + await _sign(this, this.#vault, index, block, frontier) } /** diff --git a/src/lib/wallet/ledger.ts b/src/lib/wallet/ledger.ts index 70a9d87..09f2a48 100644 --- a/src/lib/wallet/ledger.ts +++ b/src/lib/wallet/ledger.ts @@ -156,8 +156,7 @@ export class Ledger { * @param {Block} block - Block data to sign * @param {Block} [frontier] - Previous block data to cache in the device */ - static async sign (index: number, block: Block, frontier?: Block): Promise - static async sign (index: number, block: Block, frontier?: Block): Promise { + static async sign (index: number, block: Block, frontier?: Block): Promise { try { if (typeof index !== 'number') { throw new TypeError('Index must be a number', { cause: index }) @@ -182,7 +181,7 @@ export class Ledger { if (signature == null) { throw new Error('Ledger silently failed to return signature') } - block.signature = signature + return signature } catch (err) { console.error(err) throw new Error('Failed to sign block with Ledger', { cause: err }) diff --git a/src/lib/wallet/refresh.ts b/src/lib/wallet/refresh.ts index 148fc14..54b6393 100644 --- a/src/lib/wallet/refresh.ts +++ b/src/lib/wallet/refresh.ts @@ -1,8 +1,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +import { BlockInfo } from '#types' import { Wallet } from '#wallet' import { AccountList } from '../account' +import { Block } from '../block' import { Rpc } from '../rpc' export async function _refresh (wallet: Wallet, rpc: Rpc | string | URL, from: number, to: number): Promise @@ -24,14 +26,25 @@ export async function _refresh (wallet: Wallet, rpc: unknown, from: unknown, to: addresses.push(account.address) } const data = { + json_block: true, accounts: addresses } const { balances } = await rpc.call('accounts_balances', data) as { balances: { [address: string]: { balance: string, receivable: string } } } const { frontiers } = await rpc.call('accounts_frontiers', data) as { frontiers: { [address: string]: string } } + const { blocks } = await rpc.call('blocks_info', { json_block: true, hashes: Object.values(frontiers) }) as BlockInfo for (const account of accounts) { account.balance = balances[account.address]?.balance account.receivable = balances[account.address]?.receivable - account.frontier_block = frontiers[account.address] + if (frontiers[account.address] != null) { + account.frontier = frontiers[account.address] + const { subtype, contents: { balance, link, previous, representative, signature } } = blocks[account.frontier] + const frontierBlock = new Block(account, balance, previous, representative) + if (typeof frontierBlock[subtype] !== 'function') { + throw new TypeError('Unknown frontier block subtype', { cause: subtype }) + } + frontierBlock[subtype](link, 0).sign(signature) + account.frontier_block = frontierBlock + } } return accounts } catch (err) { diff --git a/src/lib/wallet/sign.ts b/src/lib/wallet/sign.ts index b3bb3ff..c36326d 100644 --- a/src/lib/wallet/sign.ts +++ b/src/lib/wallet/sign.ts @@ -7,8 +7,8 @@ import { Block } from '../block' import { bytes, hex } from '../convert' import { Ledger } from './ledger' -export async function _sign (wallet: Wallet, vault: Vault, index: number, block: Block): Promise -export async function _sign (wallet: Wallet, vault: Vault, index: unknown, block: unknown): Promise { +export async function _sign (wallet: Wallet, vault: Vault, index: number, block: Block, frontier?: Block): Promise +export async function _sign (wallet: Wallet, vault: Vault, index: unknown, block: unknown, frontier?: unknown): Promise { try { if (typeof index !== 'number') { throw new TypeError('Index must be a number', { cause: index }) @@ -18,15 +18,14 @@ export async function _sign (wallet: Wallet, vault: Vault, index: unknown, block } if (wallet.type === 'Ledger') { const account = await wallet.account(index) - const frontier = account.confirmed_frontier_block if (frontier && frontier instanceof Block) { try { await Ledger.updateCache(index, frontier) } catch (err) { - console.warn('Error updating Ledger cache of previous block, attempting signature anyway', err) + console.warn('Error updating Ledger cache of frontier block, attempting signature anyway', err) } } - await Ledger.sign(index, block) + block.signature = await Ledger.sign(index, block) } else { const { signature } = await vault.request({ action: 'sign', diff --git a/src/types.d.ts b/src/types.d.ts index f38c9ce..0124879 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -374,6 +374,18 @@ export declare class Block { verify (key?: string): Promise } +export type BlockInfo = { + blocks: { + [hash: string]: { + [p: string]: string + } & { + contents: { + [p: string]: string + } + } + } +} + export type Data = boolean | number | number[] | string | string[] | ArrayBuffer | CryptoKey | { [key: string]: Data } export type NamedData = { diff --git a/test/test.refresh-accounts.mjs b/test/test.refresh-accounts.mjs index 89b30a8..cbdb14a 100644 --- a/test/test.refresh-accounts.mjs +++ b/test/test.refresh-accounts.mjs @@ -41,10 +41,10 @@ await Promise.all([ //@ts-expect-error assert.ok(account.balance >= 0n) - assert.exists(account.frontier_block) - assert.equal(typeof account.frontier_block, 'string') - assert.notEqual(account.frontier_block, '') - assert.ok(/^[A-F0-9]{64}$/i.test(account.frontier_block ?? '')) + assert.exists(account.frontier) + assert.equal(typeof account.frontier, 'string') + assert.notEqual(account.frontier, '') + assert.ok(/^[A-F0-9]{64}$/i.test(account.frontier ?? '')) assert.exists(account.representative) assert.notEqual(account.representative, '') @@ -173,8 +173,8 @@ await Promise.all([ assert.ok(account instanceof Account) assert.equal(typeof account.balance, 'bigint') - assert.exists(account.frontier_block) - assert.equal(typeof account.frontier_block, 'string') + assert.exists(account.frontier) + assert.equal(typeof account.frontier, 'string') await assert.resolves(wallet.destroy()) })