Lint utils directory (#1563)

* Lint the utils directory

* Modify computeLedgerHash to take new Ledger format.
This commit is contained in:
Nathan Nichols
2021-09-10 12:32:38 -07:00
committed by Mayukha Vadari
parent 33f83947f1
commit 633032ddd8
29 changed files with 921 additions and 883 deletions

16
package-lock.json generated
View File

@@ -29,7 +29,7 @@
"@types/puppeteer": "5.4.4",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.0.0",
"@xrplf/eslint-config": "^1.2.2",
"@xrplf/eslint-config": "^1.3",
"@xrplf/prettier-config": "^1.2.0",
"assert": "^2.0.0",
"buffer": "^6.0.2",
@@ -1315,9 +1315,9 @@
}
},
"node_modules/@xrplf/eslint-config": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.2.2.tgz",
"integrity": "sha512-mZ4m0Q/3eE5dKbC1z875yrrcq9/X6O1g8uxqJSYSoWPY1AlwvfqSLm6qoe4CJ5v8eaOGFNWQGWF6a4na8pYoDw==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.3.0.tgz",
"integrity": "sha512-Zk3ZgzHxj8vozA87LF6Gv4B9v8KXH51Q4oW2sG4fCFHsqiSWVG+n4ZPr7DhTqPNhaxV21yL3OqsT7G6FgD1wXg==",
"dev": true,
"dependencies": {
"confusing-browser-globals": "^1.0.9",
@@ -1340,7 +1340,7 @@
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-tsdoc": "^0.2.5",
"prettier": "^2.0.5",
"typescript": "^3.9.3"
"typescript": ">=3.9.0"
}
},
"node_modules/@xrplf/prettier-config": {
@@ -9981,9 +9981,9 @@
"requires": {}
},
"@xrplf/eslint-config": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.2.2.tgz",
"integrity": "sha512-mZ4m0Q/3eE5dKbC1z875yrrcq9/X6O1g8uxqJSYSoWPY1AlwvfqSLm6qoe4CJ5v8eaOGFNWQGWF6a4na8pYoDw==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.3.0.tgz",
"integrity": "sha512-Zk3ZgzHxj8vozA87LF6Gv4B9v8KXH51Q4oW2sG4fCFHsqiSWVG+n4ZPr7DhTqPNhaxV21yL3OqsT7G6FgD1wXg==",
"dev": true,
"requires": {
"confusing-browser-globals": "^1.0.9",

View File

@@ -39,7 +39,7 @@
"@types/puppeteer": "5.4.4",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.0.0",
"@xrplf/eslint-config": "^1.2.2",
"@xrplf/eslint-config": "^1.3",
"@xrplf/prettier-config": "^1.2.0",
"assert": "^2.0.0",
"buffer": "^6.0.2",

View File

@@ -1,11 +1,19 @@
import { classicAddressToXAddress } from 'ripple-address-codec'
import { deriveKeypair, deriveAddress } from 'ripple-keypairs'
function deriveXAddress(options: {
interface DeriveOptions {
publicKey: string
tag: number | false
test: boolean
}): string {
}
/**
* Derive an X-Address from a public key and a destination tag.
*
* @param options - Public key and destination tag to encode as an X-Address.
* @returns X-Address.
*/
function deriveXAddress(options: DeriveOptions): string {
const classicAddress = deriveAddress(options.publicKey)
return classicAddressToXAddress(classicAddress, options.tag, options.test)
}

View File

@@ -1,13 +1,12 @@
import { classicAddressToXAddress } from 'ripple-address-codec'
import keypairs from 'ripple-keypairs'
import { errors } from '../common'
import ECDSA from '../common/ecdsa'
import { UnexpectedError } from '../common/errors'
export interface GeneratedAddress {
xAddress: string
classicAddress?: string
address?: string // @deprecated Use `classicAddress` instead.
secret: string
}
@@ -27,7 +26,14 @@ export interface GenerateAddressOptions {
includeClassicAddress?: boolean
}
// TODO: move this function to be a static function of the Wallet class (Along with its helper data types)
/**
* TODO: Move this function to be a static function of the Wallet Class.
* TODO: Doc this function.
*
* @param options - Options for generating X-Address.
* @returns A generated address.
* @throws When cannot generate an address.
*/
function generateXAddress(
options: GenerateAddressOptions = {},
): GeneratedAddress {
@@ -44,7 +50,7 @@ function generateXAddress(
const secret = keypairs.generateSeed(generateSeedOptions)
const keypair = keypairs.deriveKeypair(secret)
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
const returnValue: any = {
const returnValue: GeneratedAddress = {
xAddress: classicAddressToXAddress(
classicAddress,
false,
@@ -54,11 +60,14 @@ function generateXAddress(
}
if (options.includeClassicAddress) {
returnValue.classicAddress = classicAddress
returnValue.address = classicAddress
}
return returnValue
} catch (error) {
throw new errors.UnexpectedError(error.message)
if (error instanceof Error) {
throw new UnexpectedError(error.message)
}
throw error
}
}

View File

@@ -40,7 +40,7 @@ Compute the hash of an account, given the account's classic address (starting wi
Compute the hash of an account's SignerList.
### computeOrderID = (address: string, sequence: number): string
### computeOfferID = (address: string, sequence: number): string
Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction.

View File

@@ -12,29 +12,29 @@
*/
enum HashPrefix {
// transaction plus signature to give transaction ID
TRANSACTION_ID = 0x54584e00, // 'TXN'
// transaction plus signature to give transaction ID 'TXN'
TRANSACTION_ID = 0x54584e00,
// transaction plus metadata
TRANSACTION_NODE = 0x534e4400, // 'TND'
// transaction plus metadata 'TND'
TRANSACTION_NODE = 0x534e4400,
// inner node in tree
INNER_NODE = 0x4d494e00, // 'MIN'
// inner node in tree 'MIN'
INNER_NODE = 0x4d494e00,
// leaf node in tree
LEAF_NODE = 0x4d4c4e00, // 'MLN'
// leaf node in tree 'MLN'
LEAF_NODE = 0x4d4c4e00,
// inner transaction to sign
TRANSACTION_SIGN = 0x53545800, // 'STX'
// inner transaction to sign 'STX'
TRANSACTION_SIGN = 0x53545800,
// inner transaction to sign (TESTNET)
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'
// inner transaction to sign (TESTNET) 'stx'
TRANSACTION_SIGN_TESTNET = 0x73747800,
// inner transaction to multisign
TRANSACTION_MULTISIGN = 0x534d5400, // 'SMT'
// inner transaction to multisign 'SMT'
TRANSACTION_MULTISIGN = 0x534d5400,
// ledger
LEDGER = 0x4c575200, // 'LWR'
// ledger 'LWR'
LEDGER = 0x4c575200,
}
export default HashPrefix

View File

@@ -1,104 +1,43 @@
/* eslint-disable @typescript-eslint/no-magic-numbers -- this file mimics
behavior in rippled. Magic numbers are used for lengths and conditions */
/* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses
bitwise operators for and-ing numbers with a mask and bit shifting. */
import BigNumber from 'bignumber.js'
import { decodeAccountID } from 'ripple-address-codec'
import { decode, encode } from 'ripple-binary-codec'
import { ValidationError } from '../../common/errors'
import { Transaction } from '../../models/transactions'
import HashPrefix from './hashPrefix'
import computeLedgerHash, {
computeLedgerHeaderHash,
computeSignedTransactionHash,
computeTransactionTreeHash,
computeStateTreeHash,
} from './ledgerHash'
import ledgerSpaces from './ledgerSpaces'
import sha512Half from './sha512Half'
import { SHAMap, NodeType } from './shamap'
const BITS_IN_HEX = 16
const HEX = 16
const BYTE_LENGTH = 4
function padLeftZero(string: string, length: number): string {
return Array(length - string.length + 1).join('0') + string
}
function intToHex(integer: number, byteLength: number): string {
return padLeftZero(Number(integer).toString(BITS_IN_HEX), byteLength * 2)
}
function bytesToHex(bytes: number[]): string {
return Buffer.from(bytes).toString('hex')
}
function bigintToHex(
integerString: string | number | BigNumber,
byteLength: number,
): string {
const hex = new BigNumber(integerString).toString(16)
return padLeftZero(hex, byteLength * 2)
}
function ledgerSpaceHex(name: string): string {
return intToHex(ledgerSpaces[name].charCodeAt(0), 2)
}
function addressToHex(address: string): string {
return Buffer.from(decodeAccountID(address)).toString('hex')
}
function currencyToHex(currency: string): string {
if (currency.length === 3) {
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
bytes[12] = currency.charCodeAt(0) & 0xff
bytes[13] = currency.charCodeAt(1) & 0xff
bytes[14] = currency.charCodeAt(2) & 0xff
return bytesToHex(bytes)
function ledgerSpaceHex(name: keyof typeof ledgerSpaces): string {
return ledgerSpaces[name].charCodeAt(0).toString(HEX).padStart(4, '0')
}
const MASK = 0xff
function currencyToHex(currency: string): string {
if (currency.length !== 3) {
return currency
}
function addLengthPrefix(hex: string): string {
const length = hex.length / 2
if (length <= 192) {
return bytesToHex([length]) + hex
}
if (length <= 12480) {
const x = length - 193
// eslint-disable-next-line no-bitwise -- adding a prefix to hex requires bitwise operations
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex
}
if (length <= 918744) {
const x = length - 12481
// eslint-disable-next-line no-bitwise -- adding a prefix to hex requires bitwise operations
return bytesToHex([241 + (x >>> 16), (x >>> 8) & 0xff, x & 0xff]) + hex
}
throw new Error('Variable integer overflow.')
}
/**
* Hashes the Transaction object as the ledger does. Throws if the transaction is unsigned.
*
* @param tx - A transaction to hash. Tx may be in binary blob form. Tx must be signed.
* @returns A hash of tx.
* @throws ValidationError if the Transaction is unsigned.
*/
export function computeSignedTransactionHash(tx: Transaction | string): string {
let txBlob
let txObject
if (typeof tx === 'string') {
txBlob = tx
txObject = decode(tx)
} else {
txBlob = encode(tx)
txObject = tx
}
if (
txObject.TxnSignature === undefined &&
(txObject.Signers === undefined ||
txObject.Signers[0].Signer.TxnSignature === undefined)
) {
throw new ValidationError('The transaction must be signed to hash it.')
}
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
return sha512Half(prefix.concat(txBlob))
const bytes = Array(20).fill(0)
bytes[12] = currency.charCodeAt(0) & MASK
bytes[13] = currency.charCodeAt(1) & MASK
bytes[14] = currency.charCodeAt(2) & MASK
return Buffer.from(bytes).toString('hex')
}
/**
@@ -110,19 +49,19 @@ export function computeSignedTransactionHash(tx: Transaction | string): string {
* @returns The hash to sign.
*/
export function computeBinaryTransactionSigningHash(txBlobHex: string): string {
const prefix = HashPrefix.TRANSACTION_SIGN.toString(16).toUpperCase()
const prefix = HashPrefix.TRANSACTION_SIGN.toString(HEX).toUpperCase()
return sha512Half(prefix + txBlobHex)
}
/**
* Compute Account Root Index.
* Compute AccountRoot Ledger Object Index.
*
* All objects in a ledger's state tree have a unique index.
* The Account Root index is derived by hashing the
* All objects in a ledger's state tree have a unique Index.
* The AccountRoot Ledger Object Index is derived by hashing the
* address with a namespace identifier. This ensures every
* index is unique.
* Index is unique.
*
* See [Ledger Object IDs](https://xrpl.org/ledger-object-ids.html).
* See [Ledger Object Indexes](https://xrpl.org/ledger-object-ids.html).
*
* @param address - The classic account address.
* @returns The Ledger Object Index for the account.
@@ -132,43 +71,56 @@ export function computeAccountRootIndex(address: string): string {
}
/**
* [SignerList ID Format](https://xrpl.org/signerlist.html#signerlist-id-format).
* [SignerList Index Format](https://xrpl.org/signerlist.html#signerlist-id-format).
*
* The index of a SignerList object is the SHA-512Half of the following values, concatenated in order:
* The Index of a SignerList object is the SHA-512Half of the following values, concatenated in order:
* * The RippleState space key (0x0053)
* * The AccountID of the owner of the SignerList
* * The SignerListID (currently always 0).
*
* This method computes a SignerList index.
* This method computes a SignerList Ledger Object Index.
*
* @param address - The classic account address of the SignerList owner (starting with r).
* @returns The ID of the account's SignerList object.
* @returns The Index of the account's SignerList object.
*/
export function computeSignerListIndex(address: string): string {
return sha512Half(
`${ledgerSpaceHex('signerList') + addressToHex(address)}00000000`,
) // uint32(0) signer list index
)
}
/**
* [Offer ID Format](https://xrpl.org/offer.html#offer-id-format).
* [Offer Index Format](https://xrpl.org/offer.html#offer-id-format).
*
* The index of a Offer object is the SHA-512Half of the following values, concatenated in order:
* The Index of a Offer object is the SHA-512Half of the following values, concatenated in order:
* * The Offer space key (0x006F)
* * The AccountID of the account placing the offer
* * The Sequence number of the OfferCreate transaction that created the offer.
*
* This method computes an Offer Index (aka Order Index).
* This method computes an Offer Index.
*
* @param address - The classic account address of the SignerList owner (starting with r).
* @param sequence
* @returns The index of the account's Offer object.
* @param sequence - Sequence of the Offer.
* @returns The Index of the account's Offer object.
*/
export function computeOfferIndex(address: string, sequence: number): string {
const prefix = `00${intToHex(ledgerSpaces.offer.charCodeAt(0), 1)}`
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
const hexPrefix = ledgerSpaces.offer
.charCodeAt(0)
.toString(HEX)
.padStart(2, '0')
const hexSequence = sequence.toString(HEX).padStart(8, '0')
const prefix = `00${hexPrefix}`
return sha512Half(prefix + addressToHex(address) + hexSequence)
}
/**
* Compute the hash of a Trustline.
*
* @param address1 - One of the addresses in the Trustline.
* @param address2 - The other address in the Trustline.
* @param currency - Currency in the Trustline.
* @returns The hash of the Trustline.
*/
export function computeTrustlineHash(
address1: string,
address2: string,
@@ -189,56 +141,29 @@ export function computeTrustlineHash(
)
}
export function computeTransactionTreeHash(transactions: any[]): string {
const shamap = new SHAMap()
transactions.forEach((txJSON) => {
const txBlobHex = encode(txJSON)
const metaHex = encode(txJSON.metaData)
const txHash = computeSignedTransactionHash(txBlobHex)
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
})
return shamap.hash
}
export function computeStateTreeHash(entries: any[]): string {
const shamap = new SHAMap()
entries.forEach((ledgerEntry) => {
const data = encode(ledgerEntry)
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
})
return shamap.hash
}
// see rippled Ledger::calculateLedgerHash()
export function computeLedgerHash(ledgerHeader): string {
const prefix = HashPrefix.LEDGER.toString(16).toUpperCase()
return sha512Half(
prefix +
intToHex(ledgerHeader.ledger_index, 4) +
bigintToHex(ledgerHeader.total_coins, 8) +
ledgerHeader.parent_hash +
ledgerHeader.transaction_hash +
ledgerHeader.account_hash +
intToHex(ledgerHeader.parent_close_time, 4) +
intToHex(ledgerHeader.close_time, 4) +
intToHex(ledgerHeader.close_time_resolution, 1) +
intToHex(ledgerHeader.close_flags, 1),
)
}
/**
* Compute the Hash of an Escrow LedgerEntry.
*
* @param address - Address of the Escrow.
* @param sequence - OfferSequence of the Escrow.
* @returns The hash of the Escrow LedgerEntry.
*/
export function computeEscrowHash(address: string, sequence: number): string {
return sha512Half(
ledgerSpaceHex('escrow') +
addressToHex(address) +
intToHex(sequence, BYTE_LENGTH),
sequence.toString(HEX).padStart(BYTE_LENGTH * 2, '0'),
)
}
/**
* Compute the hash of a Payment Channel.
*
* @param address - Account of the Payment Channel.
* @param dstAddress - Destination Account of the Payment Channel.
* @param sequence - Sequence number of the Transaction that created the Payment Channel.
* @returns Hash of the Payment Channel.
*/
export function computePaymentChannelHash(
address: string,
dstAddress: string,
@@ -248,6 +173,14 @@ export function computePaymentChannelHash(
ledgerSpaceHex('paychan') +
addressToHex(address) +
addressToHex(dstAddress) +
intToHex(sequence, BYTE_LENGTH),
sequence.toString(HEX).padStart(BYTE_LENGTH * 2, '0'),
)
}
export {
computeLedgerHeaderHash,
computeSignedTransactionHash,
computeLedgerHash,
computeStateTreeHash,
computeTransactionTreeHash,
}

View File

@@ -0,0 +1,230 @@
/* eslint-disable @typescript-eslint/no-magic-numbers -- this file mimics
behavior in rippled. Magic numbers are used for lengths and conditions */
/* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses
bitwise operators for and-ing numbers with a mask and bit shifting. */
import BigNumber from 'bignumber.js'
import { decode, encode } from 'ripple-binary-codec'
import { ValidationError } from '../../common/errors'
import type { Ledger } from '../../models/ledger'
import { LedgerEntry } from '../../models/ledger'
import { Transaction } from '../../models/transactions'
import Metadata from '../../models/transactions/metadata'
import HashPrefix from './hashPrefix'
import sha512Half from './sha512Half'
import SHAMap, { NodeType } from './shaMap'
const HEX = 16
interface ComputeLedgerHeaderHashOptions {
computeTreeHashes?: boolean
}
function intToHex(integer: number, byteLength: number): string {
const foo = Number(integer)
.toString(HEX)
.padStart(byteLength * 2, '0')
return foo
}
function bytesToHex(bytes: number[]): string {
return Buffer.from(bytes).toString('hex')
}
function bigintToHex(
integerString: string | number | BigNumber,
byteLength: number,
): string {
const hex = new BigNumber(integerString).toString(HEX)
return hex.padStart(byteLength * 2, '0')
}
function addLengthPrefix(hex: string): string {
const length = hex.length / 2
if (length <= 192) {
return bytesToHex([length]) + hex
}
if (length <= 12480) {
const prefix = length - 193
return bytesToHex([193 + (prefix >>> 8), prefix & 0xff]) + hex
}
if (length <= 918744) {
const prefix = length - 12481
return (
bytesToHex([
241 + (prefix >>> 16),
(prefix >>> 8) & 0xff,
prefix & 0xff,
]) + hex
)
}
throw new Error('Variable integer overflow.')
}
/**
* Hashes the Transaction object as the ledger does. Throws if the transaction is unsigned.
*
* @param tx - A transaction to hash. Tx may be in binary blob form. Tx must be signed.
* @returns A hash of tx.
* @throws ValidationError if the Transaction is unsigned.
*/
export function computeSignedTransactionHash(tx: Transaction | string): string {
let txBlob: string
let txObject: Transaction
if (typeof tx === 'string') {
txBlob = tx
// TODO: type ripple-binary-codec with Transaction
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Required until updated in binary codec. */
txObject = decode(tx) as unknown as Transaction
} else {
txBlob = encode(tx)
txObject = tx
}
if (txObject.TxnSignature === undefined && txObject.Signers === undefined) {
throw new ValidationError('The transaction must be signed to hash it.')
}
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
return sha512Half(prefix.concat(txBlob))
}
/**
* Compute the hash of a ledger.
*
* @param ledgerHeader - Ledger to compute the hash of.
* @returns The hash of the ledger.
*/
export function computeLedgerHeaderHash(ledgerHeader: Ledger): string {
const prefix = HashPrefix.LEDGER.toString(HEX).toUpperCase()
const ledger =
prefix +
intToHex(Number(ledgerHeader.ledger_index), 4) +
bigintToHex(ledgerHeader.total_coins, 8) +
ledgerHeader.parent_hash +
ledgerHeader.transaction_hash +
ledgerHeader.account_hash +
intToHex(ledgerHeader.parent_close_time, 4) +
intToHex(ledgerHeader.close_time, 4) +
intToHex(ledgerHeader.close_time_resolution, 1) +
intToHex(ledgerHeader.close_flags, 1)
return sha512Half(ledger)
}
/**
* Compute the root hash of the SHAMap containing all transactions.
*
* @param transactions - List of Transactions.
* @returns The root hash of the SHAMap.
*/
export function computeTransactionTreeHash(
transactions: Array<Transaction & { metaData?: Metadata }>,
): string {
const shamap = new SHAMap()
for (const txJSON of transactions) {
const txBlobHex = encode(txJSON)
const metaHex = encode(txJSON.metaData ?? {})
const txHash = computeSignedTransactionHash(txBlobHex)
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
}
return shamap.hash
}
/**
* Compute the state hash of a list of LedgerEntries.
*
* @param entries - List of LedgerEntries.
* @returns Hash of SHAMap that consists of all entries.
*/
export function computeStateTreeHash(entries: LedgerEntry[]): string {
const shamap = new SHAMap()
entries.forEach((ledgerEntry) => {
const data = encode(ledgerEntry)
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
})
return shamap.hash
}
export function computeTransactionHash(
ledger: Ledger,
options: ComputeLedgerHeaderHashOptions,
): string {
const { transaction_hash } = ledger
if (!options.computeTreeHashes) {
return transaction_hash
}
if (ledger.transactions == null) {
throw new ValidationError('transactions is missing from the ledger')
}
const transactionHash = computeTransactionTreeHash(ledger.transactions)
if (transaction_hash !== transactionHash) {
throw new ValidationError(
'transactionHash in header' +
' does not match computed hash of transactions',
{
transactionHashInHeader: transaction_hash,
computedHashOfTransactions: transactionHash,
},
)
}
return transactionHash
}
export function computeStateHash(
ledger: Ledger,
options: ComputeLedgerHeaderHashOptions,
): string {
const { account_hash } = ledger
if (!options.computeTreeHashes) {
return account_hash
}
if (ledger.accountState == null) {
throw new ValidationError('accountState is missing from the ledger')
}
const stateHash = computeStateTreeHash(ledger.accountState)
if (account_hash !== stateHash) {
throw new ValidationError(
'stateHash in header does not match computed hash of state',
)
}
return stateHash
}
/**
* Compute the hash of a ledger.
*
* @param ledger - Ledger to compute the hash for.
* @param options - Allow client to recompute Transaction and State Hashes.
* @returns The has of ledger.
*/
function computeLedgerHash(
ledger: Ledger,
options: ComputeLedgerHeaderHashOptions = {},
): string {
const subhashes = {
transaction_hash: computeTransactionHash(ledger, options),
account_hash: computeStateHash(ledger, options),
}
return computeLedgerHeaderHash({ ...ledger, ...subhashes })
}
export default computeLedgerHash

View File

@@ -8,14 +8,17 @@
*
* See [LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100).
*/
export default {
const ledgerSpaces = {
account: 'a',
dirNode: 'd',
generatorMap: 'g',
rippleState: 'r',
offer: 'o', // Entry for an offer.
ownerDir: 'O', // Directory of things owned by an account.
bookDir: 'B', // Directory of order books.
// Entry for an offer.
offer: 'o',
// Directory of things owned by an account.
ownerDir: 'O',
// Directory of order books.
bookDir: 'B',
contract: 'c',
skipList: 's',
escrow: 'u',
@@ -27,3 +30,5 @@ export default {
check: 'C',
depositPreauth: 'p',
}
export default ledgerSpaces

View File

@@ -1,11 +1,19 @@
import { createHash } from 'crypto'
const sha512Half = (hex: string): string => {
const HASH_SIZE = 64
/**
* Compute a sha512Half Hash of a hex string.
*
* @param hex - Hex string to hash.
* @returns Hash of hex.
*/
function sha512Half(hex: string): string {
return createHash('sha512')
.update(Buffer.from(hex, 'hex'))
.digest('hex')
.toUpperCase()
.slice(0, 64)
.slice(0, HASH_SIZE)
}
export default sha512Half

View File

@@ -0,0 +1,37 @@
import InnerNode from './innerNode'
import Leaf from './leafNode'
import { NodeType } from './node'
class SHAMap {
public root: InnerNode
/**
* SHAMap tree constructor.
*/
public constructor() {
this.root = new InnerNode(0)
}
/**
* Add an item to the SHAMap.
*
* @param tag - Index of the Node to add.
* @param data - Data to insert into the tree.
* @param type - Type of the node to add.
*/
public addItem(tag: string, data: string, type: NodeType): void {
this.root.addItem(tag, new Leaf(tag, data, type))
}
/**
* Get the hash of the SHAMap.
*
* @returns The hash of the root of the SHAMap.
*/
public get hash(): string {
return this.root.hash
}
}
export * from './node'
export default SHAMap

View File

@@ -0,0 +1,123 @@
import hashPrefix from '../hashPrefix'
import sha512Half from '../sha512Half'
import Leaf from './leafNode'
import { NodeType, Node } from './node'
const HEX_ZERO =
'0000000000000000000000000000000000000000000000000000000000000000'
const SLOT_MAX = 15
const HEX = 16
/**
* Class for SHAMap InnerNode.
*/
class InnerNode extends Node {
public leaves: { [slot: number]: Node | undefined }
public type: NodeType
public depth: number
public empty: boolean
/**
* Define an Inner (non-leaf) node in a SHAMap tree.
*
* @param depth - I.e. How many parent inner nodes.
*/
public constructor(depth = 0) {
super()
this.leaves = {}
this.type = NodeType.INNER
this.depth = depth
this.empty = true
}
/**
* Adds an item to the InnerNode.
*
* @param tag - Equates to a ledger entry `index`.
* @param node - Node to add.
* @throws If there is a index collision.
*/
public addItem(tag: string, node: Node): void {
const existingNode = this.getNode(parseInt(tag[this.depth], HEX))
if (existingNode === undefined) {
this.setNode(parseInt(tag[this.depth], HEX), node)
return
}
// A node already exists in this slot
if (existingNode instanceof InnerNode) {
// There is an inner node, so we need to go deeper
existingNode.addItem(tag, node)
} else if (existingNode instanceof Leaf) {
if (existingNode.tag === tag) {
// Collision
throw new Error(
'Tried to add a node to a SHAMap that was already in there.',
)
} else {
const newInnerNode = new InnerNode(this.depth + 1)
// Parent new and existing node
newInnerNode.addItem(existingNode.tag, existingNode)
newInnerNode.addItem(tag, node)
// And place the newly created inner node in the slot
this.setNode(parseInt(tag[this.depth], HEX), newInnerNode)
}
}
}
/**
* Overwrite the node that is currently in a given slot.
*
* @param slot - A number 0-15.
* @param node - To place.
* @throws If slot is out of range.
*/
public setNode(slot: number, node: Node): void {
if (slot < 0 || slot > SLOT_MAX) {
throw new Error('Invalid slot: slot must be between 0-15.')
}
this.leaves[slot] = node
this.empty = false
}
/**
* Get the node that is currently in a given slot.
*
* @param slot - A number 0-15.
* @returns Node currently in a slot.
* @throws If slot is out of range.
*/
public getNode(slot: number): Node | undefined {
if (slot < 0 || slot > SLOT_MAX) {
throw new Error('Invalid slot: slot must be between 0-15.')
}
return this.leaves[slot]
}
/**
* Get the hash of a LeafNode.
*
* @returns Hash of the LeafNode.
*/
public get hash(): string {
if (this.empty) {
return HEX_ZERO
}
let hex = ''
for (let iter = 0; iter <= SLOT_MAX; iter++) {
const child = this.leaves[iter]
const hash: string = child == null ? HEX_ZERO : child.hash
hex += hash
}
const prefix = hashPrefix.INNER_NODE.toString(HEX)
return sha512Half(prefix + hex)
}
}
export default InnerNode

View File

@@ -0,0 +1,67 @@
import hashPrefix from '../hashPrefix'
import sha512Half from '../sha512Half'
import { NodeType, Node } from './node'
const HEX = 16
/**
* Class for SHAMap Leaf Node.
*/
class Leaf extends Node {
public tag: string
public type: NodeType
public data: string
/**
* Leaf node in a SHAMap tree.
*
* @param tag - Equates to a ledger entry `index`.
* @param data - Hex of account state, transaction etc.
* @param type - One of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc.
*/
public constructor(tag: string, data: string, type: NodeType) {
super()
this.tag = tag
this.type = type
this.data = data
}
/**
* Add item to Leaf.
*
* @param tag - Index of the Node.
* @param node - Node to insert.
* @throws When called, because LeafNodes cannot addItem.
*/
public addItem(tag: string, node: Node): void {
throw new Error('Cannot call addItem on a LeafNode')
}
/**
* Get the hash of a LeafNode.
*
* @returns Hash or undefined.
* @throws If node is of unknown type.
*/
public get hash(): string {
switch (this.type) {
case NodeType.ACCOUNT_STATE: {
const leafPrefix = hashPrefix.LEAF_NODE.toString(HEX)
return sha512Half(leafPrefix + this.data + this.tag)
}
case NodeType.TRANSACTION_NO_METADATA: {
const txIDPrefix = hashPrefix.TRANSACTION_ID.toString(HEX)
return sha512Half(txIDPrefix + this.data)
}
case NodeType.TRANSACTION_METADATA: {
const txNodePrefix = hashPrefix.TRANSACTION_NODE.toString(HEX)
return sha512Half(txNodePrefix + this.data + this.tag)
}
default:
throw new Error('Tried to hash a SHAMap node of unknown type.')
}
}
}
export default Leaf

View File

@@ -0,0 +1,14 @@
export enum NodeType {
INNER = 1,
TRANSACTION_NO_METADATA = 2,
TRANSACTION_METADATA = 3,
ACCOUNT_STATE = 4,
}
/**
* Abstract base class for SHAMapNode.
*/
export abstract class Node {
public abstract addItem(_tag: string, _node: Node): void
public abstract get hash(): string
}

View File

@@ -1,191 +0,0 @@
import hashPrefix from './hashPrefix'
import sha512Half from './sha512Half'
const HEX_ZERO =
'0000000000000000000000000000000000000000000000000000000000000000'
export enum NodeType {
INNER = 1,
TRANSACTION_NO_METADATA = 2,
TRANSACTION_METADATA = 3,
ACCOUNT_STATE = 4,
}
export abstract class Node {
/**
* Abstract constructor representing a node in a SHAMap tree.
* Can be either InnerNode or Leaf.
*
* @class
*/
public constructor() {}
public addItem(_tag: string, _node: Node): void {
throw new Error(
'Called unimplemented virtual method SHAMapTreeNode#addItem.',
)
}
public get hash(): string | void {
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.')
}
}
export class InnerNode extends Node {
public leaves: { [slot: number]: Node }
public type: NodeType
public depth: number
public empty: boolean
/**
* Define an Inner (non-leaf) node in a SHAMap tree.
*
* @param depth - I.e. How many parent inner nodes.
* @class
*/
public constructor(depth = 0) {
super()
this.leaves = {}
this.type = NodeType.INNER
this.depth = depth
this.empty = true
}
/**
* @param {string} tag - Equates to a ledger entry `index`.
* @param {Node} node - To add.
* @returns {void}
*/
public addItem(tag: string, node: Node): void {
const existingNode = this.getNode(parseInt(tag[this.depth], 16))
if (existingNode) {
// A node already exists in this slot
if (existingNode instanceof InnerNode) {
// There is an inner node, so we need to go deeper
existingNode.addItem(tag, node)
} else if (existingNode instanceof Leaf) {
if (existingNode.tag === tag) {
// Collision
throw new Error(
'Tried to add a node to a SHAMap that was already in there.',
)
} else {
// Turn it into an inner node
const newInnerNode = new InnerNode(this.depth + 1)
// Parent new and existing node
newInnerNode.addItem(existingNode.tag, existingNode)
newInnerNode.addItem(tag, node)
// And place the newly created inner node in the slot
this.setNode(parseInt(tag[this.depth], 16), newInnerNode)
}
}
} else {
// Neat, we have a nice open spot for the new node
this.setNode(parseInt(tag[this.depth], 16), node)
}
}
/**
* Overwrite the node that is currently in a given slot.
*
* @param slot - A number 0-15.
* @param node - To place.
* @returns
*/
public setNode(slot: number, node: Node): void {
if (slot < 0 || slot > 15) {
throw new Error('Invalid slot: slot must be between 0-15.')
}
this.leaves[slot] = node
this.empty = false
}
/**
* Get the node that is currently in a given slot.
*
* @param slot - A number 0-15.
* @returns
*/
public getNode(slot: number): Node {
if (slot < 0 || slot > 15) {
throw new Error('Invalid slot: slot must be between 0-15.')
}
return this.leaves[slot]
}
public get hash(): string {
if (this.empty) {
return HEX_ZERO
}
let hex = ''
for (let i = 0; i < 16; i++) {
hex += this.leaves[i] ? this.leaves[i].hash : HEX_ZERO
}
const prefix = hashPrefix.INNER_NODE.toString(16)
return sha512Half(prefix + hex)
}
}
export class Leaf extends Node {
public tag: string
public type: NodeType
public data: string
/**
* Leaf node in a SHAMap tree.
*
* @param tag - Equates to a ledger entry `index`.
* @param data - Hex of account state, transaction etc.
* @param type - One of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc.
* @class
*/
public constructor(tag: string, data: string, type: NodeType) {
super()
this.tag = tag
this.type = type
this.data = data
}
public get hash(): string | void {
switch (this.type) {
case NodeType.ACCOUNT_STATE: {
const leafPrefix = hashPrefix.LEAF_NODE.toString(16)
return sha512Half(leafPrefix + this.data + this.tag)
}
case NodeType.TRANSACTION_NO_METADATA: {
const txIDPrefix = hashPrefix.TRANSACTION_ID.toString(16)
return sha512Half(txIDPrefix + this.data)
}
case NodeType.TRANSACTION_METADATA: {
const txNodePrefix = hashPrefix.TRANSACTION_NODE.toString(16)
return sha512Half(txNodePrefix + this.data + this.tag)
}
default:
throw new Error('Tried to hash a SHAMap node of unknown type.')
}
}
}
export class SHAMap {
public root: InnerNode
/**
* SHAMap tree.
*
* @class
*/
public constructor() {
this.root = new InnerNode(0)
}
public addItem(tag: string, data: string, type: NodeType): void {
this.root.addItem(tag, new Leaf(tag, data, type))
}
public get hash(): string {
return this.root.hash
}
}

View File

@@ -1,5 +1,3 @@
import BigNumber from 'bignumber.js'
import _ from 'lodash'
import { xAddressToClassicAddress } from 'ripple-address-codec'
import { ValidationError } from '../common/errors'
@@ -17,114 +15,37 @@ import {
computeTransactionTreeHash,
computeStateTreeHash,
computeLedgerHash,
computeLedgerHeaderHash,
computeEscrowHash,
computePaymentChannelHash,
} from './hashes'
import computeLedgerHeaderHash from './ledgerHash'
import signPaymentChannelClaim from './signPaymentChannelClaim'
import { rippleTimeToISOTime, ISOTimeToRippleTime } from './timeConversion'
import verifyPaymentChannelClaim from './verifyPaymentChannelClaim'
import { xrpToDrops, dropsToXrp } from './xrpConversion'
/**
* Check if a secret is valid.
*
* @param secret - Secret to test for validity.
* @returns True if secret can be derived into a keypair.
*/
function isValidSecret(secret: string): boolean {
try {
deriveKeypair(secret)
return true
} catch (err) {
} catch (_err) {
return false
}
}
function dropsToXrp(drops: BigNumber.Value): string {
if (typeof drops === 'string') {
if (!/^-?[0-9]*\.?[0-9]*$/.exec(drops)) {
throw new ValidationError(
`dropsToXrp: invalid value '${drops}',` +
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`,
)
} else if (drops === '.') {
throw new ValidationError(
`dropsToXrp: invalid value '${drops}',` +
` should be a BigNumber or string-encoded number.`,
)
}
}
// Converting to BigNumber and then back to string should remove any
// decimal point followed by zeros, e.g. '1.00'.
// Important: specify base 10 to avoid exponential notation, e.g. '1e-7'.
drops = new BigNumber(drops).toString(10)
// drops are only whole units
if (drops.includes('.')) {
throw new ValidationError(
`dropsToXrp: value '${drops}' has` + ` too many decimal places.`,
)
}
// This should never happen; the value has already been
// validated above. This just ensures BigNumber did not do
// something unexpected.
if (!/^-?[0-9]+$/.exec(drops)) {
throw new ValidationError(
`dropsToXrp: failed sanity check -` +
` value '${drops}',` +
` does not match (^-?[0-9]+$).`,
)
}
return new BigNumber(drops).dividedBy(1000000.0).toString(10)
}
function xrpToDrops(xrp: BigNumber.Value): string {
if (typeof xrp === 'string') {
if (!/^-?[0-9]*\.?[0-9]*$/.exec(xrp)) {
throw new ValidationError(
`xrpToDrops: invalid value '${xrp}',` +
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`,
)
} else if (xrp === '.') {
throw new ValidationError(
`xrpToDrops: invalid value '${xrp}',` +
` should be a BigNumber or string-encoded number.`,
)
}
}
// Important: specify base 10 to avoid exponential notation, e.g. '1e-7'.
xrp = new BigNumber(xrp).toString(10)
// This should never happen; the value has already been
// validated above. This just ensures BigNumber did not do
// something unexpected.
if (!/^-?[0-9.]+$/.exec(xrp)) {
throw new ValidationError(
`xrpToDrops: failed sanity check -` +
` value '${xrp}',` +
` does not match (^-?[0-9.]+$).`,
)
}
const components = xrp.split('.')
if (components.length > 2) {
throw new ValidationError(
`xrpToDrops: failed sanity check -` +
` value '${xrp}' has` +
` too many decimal points.`,
)
}
const fraction = components[1] || '0'
if (fraction.length > 6) {
throw new ValidationError(
`xrpToDrops: value '${xrp}' has` + ` too many decimal places.`,
)
}
return new BigNumber(xrp)
.times(1000000.0)
.integerValue(BigNumber.ROUND_FLOOR)
.toString(10)
}
/**
* TODO: Remove/rename this function.
*
* @param amount - Convert an Amount in.
* @returns Amount without X-Address issuer.
* @throws When issuer X-Address includes a tag.
*/
function toRippledAmount(amount: RippledAmount): RippledAmount {
if (typeof amount === 'string') {
return amount
@@ -137,14 +58,14 @@ function toRippledAmount(amount: RippledAmount): RippledAmount {
return amount.value
}
let issuer = amount.counterparty || amount.issuer
let issuer = amount.counterparty ?? amount.issuer
let tag: number | false = false
try {
if (issuer) {
;({ classicAddress: issuer, tag } = xAddressToClassicAddress(issuer))
}
} catch (e) {
} catch (_e) {
/* not an X-address */
}
@@ -159,58 +80,23 @@ function toRippledAmount(amount: RippledAmount): RippledAmount {
}
}
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
if (typeof obj === 'object') {
const accumulator = Array.isArray(obj) ? [] : {}
let newKey
return Object.entries(obj).reduce((result, [key, value]) => {
newKey = key
// taking this out of function leads to error in PhantomJS
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g
if (FINDSNAKE.test(key)) {
newKey = key.replace(FINDSNAKE, (r) => r[0] + r[2].toUpperCase())
}
result[newKey] = convertKeysFromSnakeCaseToCamelCase(value)
return result
}, accumulator)
}
return obj
}
function removeUndefined<T extends object>(obj: T): T {
return _.omitBy(obj, (value) => value == null) as T
}
/**
* @param rpepoch - (seconds since 1/1/2000 GMT).
* @returns Milliseconds since unix epoch.
* Removes undefined values from an object.
*
* @param obj - Object to remove undefined values from.
* @returns The same object, but without undefined values.
*/
function rippleToUnixTimestamp(rpepoch: number): number {
return (rpepoch + 0x386d4380) * 1000
}
function removeUndefined<T extends Record<string, unknown>>(obj: T): T {
const newObj = { ...obj }
/**
* @param timestamp - (ms since unix epoch).
* @returns Seconds since Ripple Epoch (1/1/2000 GMT).
*/
function unixToRippleTimestamp(timestamp: number): number {
return Math.round(timestamp / 1000) - 0x386d4380
Object.entries(obj).forEach(([key, value]) => {
if (value == null) {
/* eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- Deletes undefined values. */
delete newObj[key]
}
})
/**
* @param rippleTime - Is the number of seconds since Ripple Epoch (1/1/2000 GMT).
* @returns Iso8601 international standard date format.
*/
function rippleTimeToISOTime(rippleTime: number): string {
return new Date(rippleToUnixTimestamp(rippleTime)).toISOString()
}
/**
* @param iso8601 - International standard date format.
* @returns Seconds since ripple epoch (1/1/2000 GMT).
*/
function ISOTimeToRippleTime(iso8601: string): number {
return unixToRippleTimestamp(Date.parse(iso8601))
return newObj
}
function convertStringToHex(string: string): string {
@@ -218,11 +104,9 @@ function convertStringToHex(string: string): string {
}
export {
computeLedgerHeaderHash,
dropsToXrp,
xrpToDrops,
toRippledAmount,
convertKeysFromSnakeCaseToCamelCase,
removeUndefined,
rippleTimeToISOTime,
ISOTimeToRippleTime,
@@ -236,6 +120,7 @@ export {
computeTransactionTreeHash,
computeStateTreeHash,
computeLedgerHash,
computeLedgerHeaderHash,
computeEscrowHash,
computePaymentChannelHash,
generateXAddress,

View File

@@ -1,126 +0,0 @@
import _ from 'lodash'
import { ValidationError } from '../common/errors'
import {
computeLedgerHash,
computeTransactionTreeHash,
computeStateTreeHash,
} from './hashes'
import { ISOTimeToRippleTime } from '.'
function convertLedgerHeader(header): any {
return {
account_hash: header.stateHash,
close_time: ISOTimeToRippleTime(header.closeTime),
close_time_resolution: header.closeTimeResolution,
close_flags: header.closeFlags,
hash: header.ledgerHash,
ledger_hash: header.ledgerHash,
ledger_index: header.ledgerVersion.toString(),
parent_hash: header.parentLedgerHash,
parent_close_time: ISOTimeToRippleTime(header.parentCloseTime),
total_coins: header.totalDrops,
transaction_hash: header.transactionHash,
}
}
function hashLedgerHeader(ledgerHeader) {
const header = convertLedgerHeader(ledgerHeader)
return computeLedgerHash(header)
}
function computeLedgerTransactionHash(
ledger,
options: ComputeLedgerHeaderHashOptions,
) {
let transactions: any[] = []
if (ledger.rawTransactions) {
transactions = JSON.parse(ledger.rawTransactions)
} else if (ledger.transactions) {
try {
transactions = ledger.transactions.map((tx) =>
JSON.parse(tx.rawTransaction),
)
} catch (e) {
if (
e.toString() ===
'SyntaxError: Unexpected' + ' token u in JSON at position 0'
) {
// one or more of the `tx.rawTransaction`s is undefined
throw new ValidationError('ledger' + ' is missing raw transactions')
}
}
} else {
if (options.computeTreeHashes) {
throw new ValidationError(
'transactions' + ' property is missing from the ledger',
)
}
return ledger.transactionHash
}
const txs = transactions.map((tx) => {
const mergeTx = { ..._.omit(tx, 'tx'), ...(tx.tx || {}) }
// rename `meta` back to `metaData`
const renameMeta = {
..._.omit(mergeTx, 'meta'),
...(tx.meta ? { metaData: tx.meta } : {}),
}
return renameMeta
})
const transactionHash = computeTransactionTreeHash(txs)
if (
ledger.transactionHash != null &&
ledger.transactionHash !== transactionHash
) {
throw new ValidationError(
'transactionHash in header' +
' does not match computed hash of transactions',
{
transactionHashInHeader: ledger.transactionHash,
computedHashOfTransactions: transactionHash,
},
)
}
return transactionHash
}
function computeLedgerStateHash(
ledger,
options: ComputeLedgerHeaderHashOptions,
) {
if (ledger.rawState == null) {
if (options.computeTreeHashes) {
throw new ValidationError(
'rawState' + ' property is missing from the ledger',
)
}
return ledger.stateHash
}
const state = JSON.parse(ledger.rawState)
const stateHash = computeStateTreeHash(state)
if (ledger.stateHash != null && ledger.stateHash !== stateHash) {
throw new ValidationError(
'stateHash in header' + ' does not match computed hash of state',
)
}
return stateHash
}
export interface ComputeLedgerHeaderHashOptions {
computeTreeHashes?: boolean
}
function computeLedgerHeaderHash(
ledger: any,
options: ComputeLedgerHeaderHashOptions = {},
): string {
const subhashes = {
transactionHash: computeLedgerTransactionHash(ledger, options),
stateHash: computeLedgerStateHash(ledger, options),
}
return hashLedgerHeader({ ...ledger, ...subhashes })
}
export default computeLedgerHeaderHash

View File

@@ -1,8 +1,16 @@
import binary from 'ripple-binary-codec'
import keypairs from 'ripple-keypairs'
import { xrpToDrops } from '.'
import { xrpToDrops } from './xrpConversion'
/**
* Sign a payment channel claim.
*
* @param channel - Channel identifier specified by the paymentChannelClaim.
* @param amount - Amount specified by the paymentChannelClaim.
* @param privateKey - Private Key to sign paymentChannelClaim with.
* @returns True if the channel is valid.
*/
function signPaymentChannelClaim(
channel: string,
amount: string,

View File

@@ -0,0 +1,46 @@
const RIPPLE_EPOCH_DIFF = 0x386d4380
/**
* Convert a ripple timestamp to a unix timestamp.
*
* @param rpepoch - (seconds since 1/1/2000 GMT).
* @returns Milliseconds since unix epoch.
*/
function rippleToUnixTimestamp(rpepoch: number): number {
return (rpepoch + RIPPLE_EPOCH_DIFF) * 1000
}
/**
* Convert a unix timestamp to a ripple timestamp.
*
* @param timestamp - (ms since unix epoch).
* @returns Seconds since Ripple Epoch (1/1/2000 GMT).
*/
function unixToRippleTimestamp(timestamp: number): number {
return Math.round(timestamp / 1000) - RIPPLE_EPOCH_DIFF
}
/**
* Convert a ripple timestamp to an Iso8601 timestamp.
*
* @param rippleTime - Is the number of seconds since Ripple Epoch (1/1/2000 GMT).
* @returns Iso8601 international standard date format.
*/
function rippleTimeToISOTime(rippleTime: number): string {
return new Date(rippleToUnixTimestamp(rippleTime)).toISOString()
}
/**
* Convert an Iso8601 timestmap to a ripple timestamp.
*
* @param iso8601 - International standard date format.
* @returns Seconds since ripple epoch (1/1/2000 GMT).
*/
function ISOTimeToRippleTime(iso8601: string): number {
return unixToRippleTimestamp(Date.parse(iso8601))
}
export {
rippleTimeToISOTime,
ISOTimeToRippleTime,
}

View File

@@ -1,8 +1,17 @@
import binary from 'ripple-binary-codec'
import keypairs from 'ripple-keypairs'
import { xrpToDrops } from '.'
import { xrpToDrops } from './xrpConversion'
/**
* Verify the signature of a payment channel claim.
*
* @param channel - Channel identifier specified by the paymentChannelClaim.
* @param amount - Amount specified by the paymentChannelClaim.
* @param signature - Signature produced from signing paymentChannelClaim.
* @param publicKey - Public key that signed the paymentChannelClaim.
* @returns True if the channel is valid.
*/
function verifyPaymentChannelClaim(
channel: string,
amount: string,

View File

@@ -1,11 +1,15 @@
{
"stateHash": "D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44",
"closeTime": "2015-08-12T01:01:10.000Z",
"parentCloseTime": "2015-08-12T01:01:00.000Z",
"closeFlags": 0,
"closeTimeResolution": 10,
"ledgerVersion": 15202439,
"parentLedgerHash": "12724A65B030C15A1573AA28B1BBB5DF3DA4589AA3623675A31CAE69B23B1C4E",
"totalDrops": "99998831688050493",
"transactionHash": "325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C8"
"account_hash": "D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44",
"close_time": 492656470,
"parent_close_time": 492656460,
"close_flags": 0,
"ledger_index": "15202439",
"close_time_human": "2015-Aug-12 01:01:10.000000000 UTC",
"close_time_resolution": 10,
"closed": true,
"hash": "F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349",
"ledger_hash": "F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349",
"parent_hash": "12724A65B030C15A1573AA28B1BBB5DF3DA4589AA3623675A31CAE69B23B1C4E",
"total_coins": "99998831688050493",
"transaction_hash": "325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C8"
}

View File

@@ -1,9 +1,5 @@
[
{
"hash": "F8F337DEE5D5B238A10AF4A4D56926BA26C83EE7AF5A5A6474340C56F9252DF3",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1608,
@@ -13,9 +9,8 @@
"SigningPubKey": "03BC0973F997BC6384BE455B163519A3E96BC2D725C37F7172D5FED5DD38E2A357",
"TxnSignature": "3045022100D80A1802B00AEEF9FDFDE594B0D568217A312D54E6337B8519C0D699841EFB96022067F6913B13D0EC2354C5A67CE0A41AE4181A09CD08A1BB0638D128D357961006",
"Account": "rDPL68aNpdfp9h59R4QT5R6B1Z2W9oRc51",
"Destination": "rE4S4Xw8euysJ3mt7gmK8EhhYEwmALpb3R"
},
"meta": {
"Destination": "rE4S4Xw8euysJ3mt7gmK8EhhYEwmALpb3R",
"metaData": {
"TransactionIndex": 6,
"AffectedNodes": [
{
@@ -60,10 +55,6 @@
}
},
{
"hash": "F8D5DE632B1D8B64E577C46912CCE483D6DF4FD4E2CF4A3D586A099DE3B27021",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 18874,
@@ -73,9 +64,8 @@
"SigningPubKey": "035D097E75D4B35345CEB30F9B1D18CB81165FE6ADD02481AA5B02B5F9C8107EE1",
"TxnSignature": "304402203D80E8BC71908AB345948AB71FB7B8DE239DD79636D96D3C5BDA2B2F192A5EEA0220686413D69BF0D813FC61DABD437AEFAAE69925D3E10FCD5B2C4D90B5AF7B883D",
"Account": "rnHScgV6wSP9sR25uYWiMo3QYNA5ybQ7cH",
"Destination": "rwnnfHDaEAwXaVji52cWWizbHVMs2Cz5K9"
},
"meta": {
"Destination": "rwnnfHDaEAwXaVji52cWWizbHVMs2Cz5K9",
"metaData": {
"TransactionIndex": 5,
"AffectedNodes": [
{
@@ -120,10 +110,6 @@
}
},
{
"hash": "E9004490A92413E92DACD621AC73FD434A8950C350F7572FFEAF4D6AAF8FC288",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1615,
@@ -133,9 +119,8 @@
"SigningPubKey": "03ACFAA11628C558AB5E7FA64705F442BDAABA6E9D318B30E010BC87CDEA8D1D7D",
"TxnSignature": "3045022100A3530C2E983FB05DFF27172C649494291F7BEBA2E6A59EEAF945CB9728D1DB5E022015BCA0E9D69760224DD7C2B68F3BC1F239D89C3397161AA3901C2E04EE31C18F",
"Account": "razcSDpwds1aTeqDphqzBr7ay1ZELYAWTm",
"Destination": "rhuqJAE2UfhGCvkR7Ve35bvm39JmRvFML4"
},
"meta": {
"Destination": "rhuqJAE2UfhGCvkR7Ve35bvm39JmRvFML4",
"metaData": {
"TransactionIndex": 4,
"AffectedNodes": [
{
@@ -180,10 +165,6 @@
}
},
{
"hash": "D44BFF924D23211B82B8F604AF6D92F260F8DD13103A96F03E48825C4A978FD6",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1674,
@@ -193,9 +174,8 @@
"SigningPubKey": "028F28D78FDA74222F4008F012247DF3BBD42B90CE4CFD87E29598196108E91B52",
"TxnSignature": "3044022065A003194D91E774D180BE47D4E086BB2624BC8F6DB7C655E135D5C6C03BBC7C02205DC961C2B7A06D701B29C2116ACF6F84CC84205FF44411576C15507852ECC31C",
"Account": "rQGLp9nChtWkdgcHjj6McvJithN2S2HJsP",
"Destination": "rEUubanepAAugnNJY1gxEZLDnk9W5NCoFU"
},
"meta": {
"Destination": "rEUubanepAAugnNJY1gxEZLDnk9W5NCoFU",
"metaData": {
"TransactionIndex": 3,
"AffectedNodes": [
{
@@ -240,10 +220,6 @@
}
},
{
"hash": "C978D915BFB17687335CBFC4B207D9E7213BCEE35B468C2EEE016CDCE4EDB6E4",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "OfferCreate",
"Sequence": 289444,
"OfferSequence": 289443,
@@ -270,9 +246,8 @@
"parsed_memo_type": "offer_comment"
}
}
]
},
"meta": {
],
"metaData": {
"TransactionIndex": 2,
"AffectedNodes": [
{
@@ -372,10 +347,6 @@
}
},
{
"hash": "31B34FD7C90CDC6CF680A814DEBC6F616C69275C0E99711F904DE088A8ED4B28",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "AccountSet",
"Flags": 2147483648,
"Sequence": 387262,
@@ -383,9 +354,8 @@
"Fee": "10500",
"SigningPubKey": "027DFE042DC2BD07D2E88DD526A5FBF816C831C25CA0BB62A3BF320A3B2BA6DB5C",
"TxnSignature": "30440220572D89688D9F9DB9874CDDDD3EBDCB5808A836982864C81F185FBC54FAD1A7B902202E09AAA6D65EECC9ACDEA7F70D8D2EE024152C7B288FA9E42C427260CF922F58",
"Account": "rn6uAt46Xi6uxA2dRCtqaJyM3aaP6V9WWM"
},
"meta": {
"Account": "rn6uAt46Xi6uxA2dRCtqaJyM3aaP6V9WWM",
"metaData": {
"TransactionIndex": 1,
"AffectedNodes": [
{
@@ -412,10 +382,6 @@
}
},
{
"hash": "260BC2964FFE6D81CB25C152F8054FFB2CE6ED04FF89D8D0D0559BC14BEF0E46",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1673,
@@ -425,9 +391,8 @@
"SigningPubKey": "02C26CF5D395A1CB352BE10D5AAB73FE27FC0AFAE0BD6121E55D097EBDCF394E11",
"TxnSignature": "304402204190B6DC7D14B1CC8DDAA87F1C01FEDA6D67D598D65E1AA19D4ADE937ED14B720220662EE404438F415AD3335B9FBA1A4C2A5F72AA387740D8A011A8C53346481B1D",
"Account": "rEE77T1E5vEFcEB9zM92jBD3rPs3kPdS1j",
"Destination": "r3AsrDRMNYaKNCofo9a5Us7R66RAzTigiU"
},
"meta": {
"Destination": "r3AsrDRMNYaKNCofo9a5Us7R66RAzTigiU",
"metaData": {
"TransactionIndex": 0,
"AffectedNodes": [
{

View File

@@ -178,7 +178,7 @@ const getOrderbook = {
}
const computeLedgerHash = {
header: { ...header, rawTransactions: JSON.stringify(transactions) },
header,
transactions,
}

View File

@@ -1,6 +1,5 @@
{
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
}

View File

@@ -1,12 +1,12 @@
{
"stateHash": "EC028EC32896D537ECCA18D18BEBE6AE99709FEFF9EF72DBD3A7819E918D8B96",
"closeTime": "2014-09-24T21:21:50.000Z",
"closeTimeResolution": 10,
"closeFlags": 0,
"ledgerHash": "0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A0F",
"ledgerVersion": 9038214,
"parentLedgerHash": "4BB9CBE44C39DC67A1BE849C7467FE1A6D1F73949EA163C38A0121A15E04FFDE",
"parentCloseTime": "2014-09-24T21:21:40.000Z",
"totalDrops": "99999973964317514",
"transactionHash": "ECB730839EB55B1B114D5D1AD2CD9A932C35BA9AB6D3A8C2F08935EAC2BAC239"
"account_hash": "EC028EC32896D537ECCA18D18BEBE6AE99709FEFF9EF72DBD3A7819E918D8B96",
"close_time": "2014-09-24T21:21:50.000Z",
"close_time_resolution": 10,
"close_flags": 0,
"ledger_hash": "0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A0F",
"ledger_index": 9038214,
"parent_hash": "4BB9CBE44C39DC67A1BE849C7467FE1A6D1F73949EA163C38A0121A15E04FFDE",
"parent_close_time": "2014-09-24T21:21:40.000Z",
"total_coins": "99999973964317514",
"transaction_hash": "ECB730839EB55B1B114D5D1AD2CD9A932C35BA9AB6D3A8C2F08935EAC2BAC239"
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import assert from 'assert'
import { SHAMap, NodeType } from '../src/utils/hashes/shamap'
import SHAMap, { NodeType } from '../src/utils/hashes/shaMap'
const TYPE_TRANSACTION_NO_METADATA = NodeType.TRANSACTION_NO_METADATA

View File

@@ -1,24 +1,34 @@
import { assert } from 'chai'
import { ValidationError } from '../../src/common/errors'
import { computeLedgerHeaderHash } from '../../src/utils'
import { computeLedgerHash } from '../../src/utils'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import { assertResultMatch } from '../testUtils'
const { computeLedgerHash: REQUEST_FIXTURES } = requests
describe('computeLedgerHash', function () {
let ledger
beforeEach(function () {
ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
if (ledger.rawState != null) {
ledger.accountState = JSON.parse(ledger.rawState)
}
})
it('given corrupt data - should fail', function () {
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
ledger.transactions[0].rawTransaction =
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"12000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","meta":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}'
ledger.parentCloseTime = ledger.closeTime
let hash: string
ledger.transactions[0] = JSON.parse(
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"12000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","metaData":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}',
)
ledger.parent_close_time = ledger.close_time
let hash
try {
hash = computeLedgerHeaderHash(ledger, { computeTreeHashes: true })
hash = computeLedgerHash(ledger, { computeTreeHashes: true })
} catch (error) {
assert(error instanceof ValidationError)
if (error instanceof ValidationError) {
assert.strictEqual(
error.message,
'transactionHash in header does not match computed hash of transactions',
@@ -29,6 +39,7 @@ describe('computeLedgerHash', function () {
computedHashOfTransactions:
'EAA1ADF4D627339450F0E95EA88B7069186DD64230BAEBDCF3EEC4D616A9FC68',
})
}
return
}
assert(
@@ -38,77 +49,68 @@ describe('computeLedgerHash', function () {
})
it('given ledger without raw transactions - should throw', function () {
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
delete ledger.transactions[0].rawTransaction
delete ledger.transactions
ledger.parentCloseTime = ledger.closeTime
let hash: string
try {
hash = computeLedgerHeaderHash(ledger, { computeTreeHashes: true })
} catch (error) {
assert(error instanceof ValidationError)
assert.strictEqual(error.message, 'ledger is missing raw transactions')
return
}
assert(
false,
`Should throw ValidationError instead of producing hash: ${hash}`,
assert.throws(
() => computeLedgerHash(ledger, { computeTreeHashes: true }),
ValidationError,
'transactions is missing from the ledger',
)
})
it('given ledger without state or transactions - only compute ledger hash', function () {
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
assert.strictEqual(
ledger.transactions[0].rawTransaction,
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"10000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","meta":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}',
ledger.transactions[0] = JSON.parse(
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"10000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","metaData":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}',
)
ledger.parentCloseTime = ledger.closeTime
const computeLedgerHash = computeLedgerHeaderHash
function testCompute(ledgerToCompute, expectedError): void {
let hash = computeLedgerHash(ledgerToCompute)
ledger.parent_close_time = ledger.close_time
function testCompute(ledgerToTest, expectedError): void {
const hash = computeLedgerHash(ledgerToTest)
assert.strictEqual(
hash,
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E',
)
// fail if required to compute tree hashes
try {
hash = computeLedgerHash(ledgerToCompute, { computeTreeHashes: true })
} catch (error) {
assert(error instanceof ValidationError)
assert.strictEqual(error.message, expectedError)
return
}
assert(
false,
`Should throw ValidationError instead of producing hash: ${hash}`,
assert.throws(
() => computeLedgerHash(ledgerToTest, { computeTreeHashes: true }),
ValidationError,
expectedError,
)
}
const transactions = ledger.transactions
delete ledger.transactions
testCompute(ledger, 'transactions property is missing from the ledger')
delete ledger.rawState
testCompute(ledger, 'transactions property is missing from the ledger')
testCompute(ledger, 'transactions is missing from the ledger')
delete ledger.accountState
testCompute(ledger, 'transactions is missing from the ledger')
ledger.transactions = transactions
testCompute(ledger, 'rawState property is missing from the ledger')
testCompute(ledger, 'accountState is missing from the ledger')
})
it('wrong hash', function () {
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
assertResultMatch(ledger, responses.getLedger.full, 'getLedger')
const newLedger = {
...ledger,
parentCloseTime: ledger.closeTime,
stateHash:
parent_close_time: ledger.close_time,
account_hash:
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44',
}
assert.throws(() => {
computeLedgerHeaderHash(newLedger)
}, /does not match computed hash of state/u)
assert.throws(
() => {
computeLedgerHash(newLedger, { computeTreeHashes: true })
},
ValidationError,
'does not match computed hash of state',
)
})
it('computeLedgerHash', function () {
const header = REQUEST_FIXTURES.header
const ledgerHash = computeLedgerHeaderHash(header)
const ledgerHash = computeLedgerHash(header)
assert.strictEqual(
ledgerHash,
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
@@ -119,21 +121,27 @@ describe('computeLedgerHash', function () {
const header = {
...REQUEST_FIXTURES.header,
transactionHash: undefined,
rawTransactions: JSON.stringify(REQUEST_FIXTURES.transactions),
rawTransactions: REQUEST_FIXTURES.transactions,
}
const ledgerHash = computeLedgerHeaderHash(header)
const ledgerHash = computeLedgerHash(header)
assert.strictEqual(
ledgerHash,
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
)
})
it('computeLedgerHash - incorrent transaction_hash', function () {
it('computeLedgerHash - incorrect transaction_hash', function () {
const header = {
...REQUEST_FIXTURES.header,
transactionHash:
transaction_hash:
'325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C9',
transactions: REQUEST_FIXTURES.transactions as any,
}
assert.throws(() => computeLedgerHeaderHash(header))
assert.throws(
() => computeLedgerHash(header, { computeTreeHashes: true }),
ValidationError,
'transactionHash in header does not match computed hash of transactions',
)
})
})

View File

@@ -168,7 +168,6 @@ describe('generateAddress', function () {
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
})
})
@@ -211,7 +210,6 @@ describe('generateAddress', function () {
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
})
})