From: Chris Duncan Date: Fri, 7 Aug 2026 16:05:24 +0000 (-0700) Subject: Refactor RPC responses implemented so far to produce full responses only and throw... X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=c7e4401d1c728baf884b8af15113aab9a47dce94;p=libnemo.git Refactor RPC responses implemented so far to produce full responses only and throw otherwise. --- diff --git a/src/lib/ledger/index.ts b/src/lib/ledger/index.ts index dfc1be3..8c1a76c 100644 --- a/src/lib/ledger/index.ts +++ b/src/lib/ledger/index.ts @@ -8,7 +8,7 @@ import TransportUSB from '@ledgerhq/hw-transport-webusb' import { Block } from '../block' import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from '../constants' import { dec } from '../convert' -import { Rpc, Schema } from '../rpc' +import { Rpc } from '../rpc' import { _account } from './account' import { _cache } from './cache' import { _close } from './close' @@ -233,27 +233,14 @@ export class Ledger { 'json_block': 'true', 'hash': input } - const res = await node.post('block_info', data) - if (!res || typeof res !== 'object' || !('ok' in res) || 'error' in res) { - throw new Error('Unable to fetch block info', { cause: res }) - } - const blockInfo = res as Record & Record<'ok', unknown> - if (!(blockInfo.contents != null && typeof blockInfo.contents === 'object')) { - throw new Error('Block info missing contents', { cause: blockInfo }) - } - const contents = {} as Record - for (const [k, v] of Object.entries(blockInfo.contents)) { - if (Schema.block_info.contents.fields.includes(k) && typeof v === 'string') { - contents[k] = v - } - } - const { account, balance, link, previous, representative, signature } = contents + const blockInfo = await node.post('block_info', data) + const { account, balance, link, previous, representative, signature } = blockInfo.contents const block = new Block(account, balance, previous, representative).receive(link, 0) block.signature = signature input = block } if (!(input instanceof Block)) { - throw new TypeError('Invalid block format') + throw new TypeError('Invalid block format for Ledger cache') } return queue(async () => _cache(this.#transport, index, input)) } diff --git a/src/lib/rpc/account_balance.ts b/src/lib/rpc/account_balance.ts index 988ce06..90121a4 100644 --- a/src/lib/rpc/account_balance.ts +++ b/src/lib/rpc/account_balance.ts @@ -1,29 +1,31 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -export type AccountBalance = { +import { RpcError } from '../errors' + +export type AccountBalanceResponse = { balance: string - pending: string + pending?: string receivable: string } -export function account_balance (body: unknown): AccountBalance { - const response: AccountBalance = { - balance: '0', - pending: '0', - receivable: '0', - } - if (body == null || typeof body !== 'object') { - return response - } - if ('balance' in body && typeof body.balance === 'string') { - response.balance = body.balance - } - if ('pending' in body && typeof body.pending === 'string') { - response.pending = body.pending - } - if ('receivable' in body && typeof body.receivable === 'string') { - response.receivable = body.receivable +export function account_balance (body: unknown): AccountBalanceResponse { + if (body != null && typeof body === 'object') { + if ('balance' in body && typeof body.balance === 'string' + && 'receivable' in body && typeof body.receivable === 'string' + ) { + const response: AccountBalanceResponse = { + balance: body.balance, + receivable: body.receivable, + } + if ('pending' in body && typeof body.pending === 'string') { + response.pending = body.pending + } + return response + } + if ('error' in body && typeof body.error === 'string') { + throw new RpcError(body.error) + } } - return response + throw new RpcError('Invalid account_balance response') } diff --git a/src/lib/rpc/accounts_balances.ts b/src/lib/rpc/accounts_balances.ts index 30bacf1..464f04a 100644 --- a/src/lib/rpc/accounts_balances.ts +++ b/src/lib/rpc/accounts_balances.ts @@ -2,58 +2,53 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { Account } from '../account' -import { AccountBalance, account_balance } from './account_balance' +import { RpcError } from '../errors' +import { AccountBalanceResponse, account_balance } from './account_balance' -type AccountsBalances = { +type AccountsBalancesResponse = { balances?: { - [address: string]: AccountBalance + [address: string]: AccountBalanceResponse } errors?: { [address: string]: string } } -export function accounts_balances (body: unknown): AccountsBalances { - const response: AccountsBalances = {} - if (body == null || typeof body !== 'object') { - return response - } - const balances = 'balances' in body ? body.balances : null - const errors = 'errors' in body ? body.errors : null +export function accounts_balances (body: unknown): AccountsBalancesResponse { + if (body != null && typeof body === 'object') { + const response: AccountsBalancesResponse = {} - if (balances != null) { - if (typeof balances === 'object') { - for (const [address, data] of Object.entries(balances) as [string, unknown][]) { + if ('errors' in body && body.errors != null && typeof body.errors === 'object') { + for (const [address, error] of Object.entries(body.errors) as [string, unknown][]) { try { Account.validate(address) - if (data == null || typeof data !== 'object') { - throw new Error('Invalid account balance data') + if (typeof error === 'string') { + throw new Error(error) } - response.balances ??= {} - response.balances[address] = account_balance(data) } catch (err: any) { response.errors ??= {} response.errors[address] = err?.message } } } - } - if (errors != null) { - if (typeof errors === 'object') { - for (const [address, error] of Object.entries(errors) as [string, unknown][]) { + if ('balances' in body && body.balances != null && typeof body.balances === 'object') { + for (const [address, data] of Object.entries(body.balances) as [string, unknown][]) { try { Account.validate(address) - if (typeof error === 'string') { - throw new Error(error) + if (data == null || typeof data !== 'object') { + throw new Error('Invalid account balance data') } + response.balances ??= {} + response.balances[address] = account_balance(data) } catch (err: any) { response.errors ??= {} - response.errors[address] = err?.message + response.errors[address] ??= err?.message } } } - } - return response + return response + } + throw new RpcError('Invalid accounts_balance response') } diff --git a/src/lib/rpc/block_info.ts b/src/lib/rpc/block_info.ts index b9dbb36..e4b98f0 100644 --- a/src/lib/rpc/block_info.ts +++ b/src/lib/rpc/block_info.ts @@ -3,18 +3,20 @@ import { RpcError } from '../errors' -export type BlockInfo = { - contents: { - account: string - balance: string - link: string - link_as_account: string - previous: string - representative: string - signature: string - type: string - work: string - } +export type BlockInfoContents = { + account: string + balance: string + link: string + link_as_account: string + previous: string + representative: string + signature: string + type: 'state' + work: string +} + +export type BlockInfoResponse = { + contents: BlockInfoContents amount: string balance: string block_account: string @@ -26,51 +28,54 @@ export type BlockInfo = { successor: string } -export function block_info (body: unknown): BlockInfo { - if (body != null && typeof body === 'object' - && 'amount' in body && typeof body.amount === 'string' - && 'balance' in body && typeof body.balance === 'string' - && 'block_account' in body && typeof body.block_account === 'string' - && 'confirmed' in body && typeof body.confirmed === 'string' - && 'height' in body && typeof body.height === 'string' - && 'linked_account' in body && typeof body.linked_account === 'string' - && 'local_timestamp' in body && typeof body.local_timestamp === 'string' - && 'subtype' in body && (body.subtype === 'change' || body.subtype === 'epoch' || body.subtype === 'open' || body.subtype === 'receive' || body.subtype === 'send') - && 'successor' in body && typeof body.successor === 'string' - && 'contents' in body && body.contents != null && typeof body.contents === 'object' - && 'account' in body.contents && typeof body.contents.account === 'string' - && 'balance' in body.contents && typeof body.contents.balance === 'string' - && 'link' in body.contents && typeof body.contents.link === 'string' - && 'link_as_account' in body.contents && typeof body.contents.link_as_account === 'string' - && 'previous' in body.contents && typeof body.contents.previous === 'string' - && 'representative' in body.contents && typeof body.contents.representative === 'string' - && 'signature' in body.contents && typeof body.contents.signature === 'string' - && 'type' in body.contents && typeof body.contents.type === 'string' - && 'work' in body.contents && typeof body.contents.work === 'string' - ) { - return { - contents: { - account: body.contents.account, - balance: body.contents.balance, - link: body.contents.link, - link_as_account: body.contents.link_as_account, - previous: body.contents.previous, - representative: body.contents.representative, - signature: body.contents.signature, - type: body.contents.type, - work: body.contents.work, - }, - amount: body.amount, - balance: body.balance, - block_account: body.block_account, - confirmed: body.confirmed, - height: body.height, - linked_account: body.linked_account, - local_timestamp: body.local_timestamp, - subtype: body.subtype, - successor: body.successor, +export function block_info (body: unknown): BlockInfoResponse { + if (body != null && typeof body === 'object') { + if ('amount' in body && typeof body.amount === 'string' + && 'balance' in body && typeof body.balance === 'string' + && 'block_account' in body && typeof body.block_account === 'string' + && 'confirmed' in body && typeof body.confirmed === 'string' + && 'height' in body && typeof body.height === 'string' + && 'linked_account' in body && typeof body.linked_account === 'string' + && 'local_timestamp' in body && typeof body.local_timestamp === 'string' + && 'subtype' in body && (body.subtype === 'change' || body.subtype === 'epoch' || body.subtype === 'open' || body.subtype === 'receive' || body.subtype === 'send') + && 'successor' in body && typeof body.successor === 'string' + && 'contents' in body && body.contents != null && typeof body.contents === 'object' + && 'account' in body.contents && typeof body.contents.account === 'string' + && 'balance' in body.contents && typeof body.contents.balance === 'string' + && 'link' in body.contents && typeof body.contents.link === 'string' + && 'link_as_account' in body.contents && typeof body.contents.link_as_account === 'string' + && 'previous' in body.contents && typeof body.contents.previous === 'string' + && 'representative' in body.contents && typeof body.contents.representative === 'string' + && 'signature' in body.contents && typeof body.contents.signature === 'string' + && 'type' in body.contents && body.contents.type === 'state' + && 'work' in body.contents && typeof body.contents.work === 'string' + ) { + return { + contents: { + account: body.contents.account, + balance: body.contents.balance, + link: body.contents.link, + link_as_account: body.contents.link_as_account, + previous: body.contents.previous, + representative: body.contents.representative, + signature: body.contents.signature, + type: body.contents.type, + work: body.contents.work, + }, + amount: body.amount, + balance: body.balance, + block_account: body.block_account, + confirmed: body.confirmed, + height: body.height, + linked_account: body.linked_account, + local_timestamp: body.local_timestamp, + subtype: body.subtype, + successor: body.successor, + } + } + if ('error' in body && typeof body.error === 'string') { + throw new RpcError(body.error) } - } else { - throw new RpcError('Invalid block_info response') } + throw new RpcError('Invalid block_info response') } diff --git a/src/lib/rpc/blocks_info.ts b/src/lib/rpc/blocks_info.ts index 1615996..cc8bc35 100644 --- a/src/lib/rpc/blocks_info.ts +++ b/src/lib/rpc/blocks_info.ts @@ -3,34 +3,29 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { hex } from '../convert' -import { BlockInfo, block_info } from './block_info' +import { RpcError } from '../errors' +import { BlockInfoResponse, block_info } from './block_info' -type BlocksInfo = { +type BlocksInfoResponse = { blocks?: { - [hash: string]: BlockInfo - }, + [hash: Hex]: BlockInfoResponse + } errors?: { - [address: string]: string - }, + [hash: string]: string + } } -export function blocks_info (body: unknown): BlocksInfo { - const response: BlocksInfo = {} - if (body == null || typeof body !== 'object') { - return response - } - const blocks = 'blocks' in body ? body.blocks : null - const errors = 'errors' in body ? body.errors : null +export function blocks_info (body: unknown): BlocksInfoResponse { + if (body != null && typeof body === 'object') { + const response: BlocksInfoResponse = {} - if (blocks != null) { - if (typeof blocks === 'object') { - for (const [hash, data] of Object.entries(blocks) as [string, unknown][]) { + if ('errors' in body && body.errors != null && typeof body.errors === 'object') { + for (const [hash, error] of Object.entries(body.errors) as [string, unknown][]) { try { - if (hex.is(hash)) { - response.blocks ??= {} - response.blocks[hash] = block_info(data) + if (hex.is(hash) && typeof error === 'string') { + throw new Error(error) } else { - throw new Error('Invalid blocks_info hash') + throw new Error('Invalid blocks_info error') } } catch (err: any) { response.errors ??= {} @@ -38,24 +33,24 @@ export function blocks_info (body: unknown): BlocksInfo { } } } - } - if (errors != null) { - if (typeof errors === 'object') { - for (const [hash, error] of Object.entries(errors) as [string, unknown][]) { + if ('blocks' in body && body.blocks != null && typeof body.blocks === 'object') { + for (const [hash, data] of Object.entries(body.blocks) as [string, unknown][]) { try { - if (hex.is(hash) && typeof error === 'string') { - throw new Error(error) + if (hex.is(hash)) { + response.blocks ??= {} + response.blocks[hash] = block_info(data) } else { - throw new Error('Invalid blocks_info error') + throw new Error('Invalid blocks_info hash') } } catch (err: any) { response.errors ??= {} - response.errors[hash] = err?.message + response.errors[hash] ??= err?.message } } } - } - return response + return response + } + throw new RpcError('Invalid blocks_info response') } diff --git a/src/lib/rpc/index.ts b/src/lib/rpc/index.ts index aed417b..1b5cb46 100644 --- a/src/lib/rpc/index.ts +++ b/src/lib/rpc/index.ts @@ -3,8 +3,6 @@ import { RpcError } from '../errors' import { Schema } from './schema' -export { Schema } - /** * Represents a Nano network node. It primarily consists of a URL which will * accept RPC calls; an optional authentication header name can be passed if an diff --git a/src/lib/rpc/process.ts b/src/lib/rpc/process.ts index 776bc9b..99b62ba 100644 --- a/src/lib/rpc/process.ts +++ b/src/lib/rpc/process.ts @@ -1,25 +1,31 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -export const process = Object.freeze({ - fields: [ - 'action', - 'async', - 'block', - 'json_block', - 'subtype', - ], - block: { - fields: [ - 'account', - 'balance', - 'link', - 'link_as_account', - 'previous', - 'representative', - 'signature', - 'type', - 'work', - ], - }, -}) +import { hex } from '../convert' +import { RpcError } from '../errors' +import { BlockInfoContents } from './block_info' + +export type ProcessRequest = { + action: 'process' + block: BlockInfoContents + async: boolean + force: boolean + json_block: boolean + subtype: 'change' | 'epoch' | 'open' | 'receive' | 'send' +} + +export type ProcessResponse = { + hash: Hex +} + +export function process (body: unknown): ProcessResponse { + if (body != null && typeof body === 'object') { + if ('hash' in body && hex.is(body.hash)) { + return { hash: body.hash } + } + if ('error' in body && typeof body.error === 'string') { + throw new RpcError(body.error) + } + } + throw new RpcError('Invalid process response') +} diff --git a/src/lib/rpc/schema.ts b/src/lib/rpc/schema.ts index bb3a7ce..295a92b 100644 --- a/src/lib/rpc/schema.ts +++ b/src/lib/rpc/schema.ts @@ -19,5 +19,5 @@ export const Schema = Object.freeze({ // accounts_representatives, block_info, blocks_info, - // process, + process, })