Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions packages/xrpl/src/models/ledger/Ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,30 @@ interface BaseLedger {
total_coins: string
/** Hash of the transaction information included in this ledger, as hex. */
transaction_hash: string
/**
* Transactions applied in this ledger version. By default, members are the
* transactions' identifying Hash strings. If the request specified expand as
* true, members are full representations of the transactions instead, in
* either JSON or binary depending on whether the request specified binary
* as true.
*/
transactions?: Array<
Transaction & {
hash: string
metaData?: TransactionMetadata
}
>
}

/**
* Expanded transaction format in API version 2.
* Transactions are returned as flat objects with the transaction fields
* directly on the object, plus `hash` and `metaData`.
*/
export type LedgerTransactionExpanded = Transaction & {
hash: string
metaData?: TransactionMetadata
}

/**
* Expanded transaction format in API version 1.
* Transactions are wrapped in an object with `tx_json` and `meta` fields.
*/
export interface LedgerTransactionExpandedV1 {
tx_json: Transaction
meta: TransactionMetadata
hash: string
validated: boolean
ledger_index: number
close_time_iso: string
ledger_hash: string
}

/**
Expand All @@ -80,6 +91,12 @@ export interface Ledger extends BaseLedger {
* The ledger index of the ledger. Represented as a number.
*/
ledger_index: number
/**
* Transactions applied in this ledger version. When expanded, members are
* full representations of the transactions as flat objects with the
* transaction fields directly on the object, plus `hash` and `metaData`.
*/
transactions?: Array<string | LedgerTransactionExpanded>
}

/**
Expand All @@ -95,6 +112,12 @@ export interface LedgerV1 extends BaseLedger {
* integer; some display it as a number.
*/
ledger_index: string
/**
* Transactions applied in this ledger version. When expanded, members are
* full representations of the transactions wrapped in objects with
* `tx_json` and `meta` fields.
*/
transactions?: Array<string | LedgerTransactionExpandedV1>
}

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/xrpl/src/models/ledger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import FeeSettings, {
FeeSettingsPostAmendmentFields,
FEE_SETTINGS_ID,
} from './FeeSettings'
import { Ledger, LedgerV1 } from './Ledger'
import {
Ledger,
LedgerV1,
LedgerTransactionExpanded,
LedgerTransactionExpandedV1,
} from './Ledger'
import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
import LedgerHashes from './LedgerHashes'
import Loan, { LoanFlags } from './Loan'
Expand Down Expand Up @@ -58,6 +63,8 @@ export {
FeeSettingsPostAmendmentFields,
Ledger,
LedgerV1,
LedgerTransactionExpanded,
LedgerTransactionExpandedV1,
LedgerEntryFilter,
LedgerEntry,
LedgerHashes,
Expand Down
16 changes: 6 additions & 10 deletions packages/xrpl/src/models/methods/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ export interface LedgerRequestExpandedAccountsOnly extends LedgerRequest {
*
* @category Requests
*/
// eslint-disable-next-line max-len -- Disable for interface declaration.
export interface LedgerRequestExpandedAccountsAndTransactions extends LedgerRequest {
export interface LedgerRequestExpandedAccountsAndTransactions
extends LedgerRequest {
expand: true
accounts: true
transactions: true
Expand Down Expand Up @@ -202,18 +202,14 @@ export interface LedgerQueueData {
max_spend_drops?: string
}

export interface LedgerBinary extends Omit<
Ledger,
'transactions' | 'accountState'
> {
export interface LedgerBinary
extends Omit<Ledger, 'transactions' | 'accountState'> {
accountState?: string[]
transactions?: string[]
}

export interface LedgerBinaryV1 extends Omit<
LedgerV1,
'transactions' | 'accountState'
> {
export interface LedgerBinaryV1
extends Omit<LedgerV1, 'transactions' | 'accountState'> {
accountState?: string[]
transactions?: string[]
}
Expand Down
5 changes: 2 additions & 3 deletions packages/xrpl/src/models/methods/simulate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ export interface SimulateBinaryResponse extends BaseResponse {
}
}

export interface SimulateJsonResponse<
T extends BaseTransaction = Transaction,
> extends BaseResponse {
export interface SimulateJsonResponse<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: {
applied: false

Expand Down
10 changes: 4 additions & 6 deletions packages/xrpl/src/models/methods/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,8 @@ interface BaseTxResult<
*
* @category Responses
*/
export interface TxResponse<
T extends BaseTransaction = Transaction,
> extends BaseResponse {
export interface TxResponse<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: BaseTxResult<typeof RIPPLED_API_V2, T> & { tx_json: T }
/**
* If true, the server was able to search all of the specified ledger
Expand All @@ -111,9 +110,8 @@ export interface TxResponse<
*
* @category ResponsesV1
*/
export interface TxV1Response<
T extends BaseTransaction = Transaction,
> extends BaseResponse {
export interface TxV1Response<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: BaseTxResult<typeof RIPPLED_API_V1, T> & T
/**
* If true, the server was able to search all of the specified ledger
Expand Down
38 changes: 32 additions & 6 deletions packages/xrpl/src/utils/hashes/hashLedger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import { decode, encode } from 'ripple-binary-codec'
import { ValidationError, XrplError } from '../../errors'
import { APIVersion } from '../../models'
import { LedgerEntry } from '../../models/ledger'
import { LedgerVersionMap } from '../../models/ledger/Ledger'
import { Transaction, TransactionMetadata } from '../../models/transactions'
import {
LedgerVersionMap,
LedgerTransactionExpanded,
LedgerTransactionExpandedV1,
} from '../../models/ledger/Ledger'
import { Transaction } from '../../models/transactions'
import { GlobalFlags } from '../../models/transactions/common'
import { hasFlag } from '../../models/utils'

Expand Down Expand Up @@ -130,9 +134,7 @@ export function hashLedgerHeader(
* @returns The root hash of the SHAMap.
* @category Utilities
*/
export function hashTxTree(
transactions: Array<Transaction & { metaData?: TransactionMetadata }>,
): string {
export function hashTxTree(transactions: LedgerTransactionExpanded[]): string {
const shamap = new SHAMap()
for (const txJSON of transactions) {
const txBlobHex = encode(txJSON)
Expand Down Expand Up @@ -163,6 +165,18 @@ export function hashStateTree(entries: LedgerEntry[]): string {
return shamap.hash
}

/**
* Normalize a v1 wrapped transaction to v2 flat format.
*/
function normalizeToV2(
tx: LedgerTransactionExpanded | LedgerTransactionExpandedV1,
): LedgerTransactionExpanded {
if ('tx_json' in tx) {
return { ...tx.tx_json, hash: tx.hash, metaData: tx.meta }
}
return tx
}

function computeTransactionHash(
ledger: LedgerVersionMap<APIVersion>,
options: HashLedgerHeaderOptions,
Expand All @@ -177,7 +191,19 @@ function computeTransactionHash(
throw new ValidationError('transactions is missing from the ledger')
}

const transactionHash = hashTxTree(ledger.transactions)
// Normalize transactions to the v2 flat format expected by hashTxTree.
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- ledger is a version union
const txs = ledger.transactions as Array<
string | LedgerTransactionExpanded | LedgerTransactionExpandedV1
>
const normalizedTransactions = txs
.filter(
(tx): tx is LedgerTransactionExpanded | LedgerTransactionExpandedV1 =>
typeof tx !== 'string',
)
.map(normalizeToV2)

const transactionHash = hashTxTree(normalizedTransactions)

if (transaction_hash !== transactionHash) {
throw new ValidationError(
Expand Down
95 changes: 95 additions & 0 deletions packages/xrpl/test/models/LedgerTransactionExpanded.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { assert } from 'chai'

import {
Ledger,
LedgerV1,
LedgerTransactionExpanded,
LedgerTransactionExpandedV1,
} from '../../src/models/ledger/Ledger'

describe('LedgerTransactionExpanded types', function () {
it('Ledger (v2) transactions use flat format with metaData', function () {
const tx: LedgerTransactionExpanded = {
Account: 'rPrioTXJgZJF8bpdXq2X73PcVPfvYqjVKd',
Amount: '1000000',
Destination: 'rsRy14FvipgqudiGmptJBhr1RtpsgfzKMM',
TransactionType: 'Payment',
Fee: '11',
Sequence: 1,
Flags: 0,
SigningPubKey: '',
TxnSignature: '',
hash: '044314FE34236A262DA692789CE5B48CA1A3CEC078B1A4ECCD65F4B61A9EB0A7',
metaData: {
AffectedNodes: [],
TransactionIndex: 0,
TransactionResult: 'tesSUCCESS',
},
}

// Verify v2 expanded transactions are assignable to Ledger.transactions
const ledgerV2: Pick<Ledger, 'transactions'> = {
transactions: [tx],
}

assert.isArray(ledgerV2.transactions)
const firstTx = ledgerV2.transactions![0]
assert.notEqual(typeof firstTx, 'string')
if (typeof firstTx !== 'string') {
assert.strictEqual(firstTx.hash, tx.hash)
assert.isDefined(firstTx.metaData)
}
})

it('Ledger transactions can also be hash strings', function () {
const ledgerV2: Pick<Ledger, 'transactions'> = {
transactions: [
'044314FE34236A262DA692789CE5B48CA1A3CEC078B1A4ECCD65F4B61A9EB0A7',
],
}

assert.isArray(ledgerV2.transactions)
assert.strictEqual(typeof ledgerV2.transactions![0], 'string')
})

it('LedgerV1 transactions use wrapped format with tx_json and meta', function () {
const tx: LedgerTransactionExpandedV1 = {
tx_json: {
Account: 'rPrioTXJgZJF8bpdXq2X73PcVPfvYqjVKd',
Amount: '1000000',
Destination: 'rsRy14FvipgqudiGmptJBhr1RtpsgfzKMM',
TransactionType: 'Payment',
Fee: '11',
Sequence: 1,
Flags: 0,
SigningPubKey: '',
TxnSignature: '',
},
meta: {
AffectedNodes: [],
TransactionIndex: 0,
TransactionResult: 'tesSUCCESS',
},
hash: '044314FE34236A262DA692789CE5B48CA1A3CEC078B1A4ECCD65F4B61A9EB0A7',
validated: true,
ledger_index: 93637993,
close_time_iso: '2025-01-22T21:13:50Z',
ledger_hash:
'058FDA696458896EC515AC19C3EDC8CD0E163A3620CA6B314165E5BAED70846A',
}

// Verify v1 expanded transactions are assignable to LedgerV1.transactions
const ledgerV1: Pick<LedgerV1, 'transactions'> = {
transactions: [tx],
}

assert.isArray(ledgerV1.transactions)
const firstTx = ledgerV1.transactions![0]
assert.notEqual(typeof firstTx, 'string')
if (typeof firstTx !== 'string') {
assert.strictEqual(firstTx.hash, tx.hash)
assert.isDefined(firstTx.tx_json)
assert.isDefined(firstTx.meta)
}
})
})
Loading