mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 12:15:51 +00:00
Lint utils directory (#1563)
* Lint the utils directory * Modify computeLedgerHash to take new Ledger format.
This commit is contained in:
committed by
Mayukha Vadari
parent
33f83947f1
commit
633032ddd8
16
package-lock.json
generated
16
package-lock.json
generated
@@ -29,7 +29,7 @@
|
|||||||
"@types/puppeteer": "5.4.4",
|
"@types/puppeteer": "5.4.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
||||||
"@typescript-eslint/parser": "^4.0.0",
|
"@typescript-eslint/parser": "^4.0.0",
|
||||||
"@xrplf/eslint-config": "^1.2.2",
|
"@xrplf/eslint-config": "^1.3",
|
||||||
"@xrplf/prettier-config": "^1.2.0",
|
"@xrplf/prettier-config": "^1.2.0",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"buffer": "^6.0.2",
|
"buffer": "^6.0.2",
|
||||||
@@ -1315,9 +1315,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@xrplf/eslint-config": {
|
"node_modules/@xrplf/eslint-config": {
|
||||||
"version": "1.2.2",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.3.0.tgz",
|
||||||
"integrity": "sha512-mZ4m0Q/3eE5dKbC1z875yrrcq9/X6O1g8uxqJSYSoWPY1AlwvfqSLm6qoe4CJ5v8eaOGFNWQGWF6a4na8pYoDw==",
|
"integrity": "sha512-Zk3ZgzHxj8vozA87LF6Gv4B9v8KXH51Q4oW2sG4fCFHsqiSWVG+n4ZPr7DhTqPNhaxV21yL3OqsT7G6FgD1wXg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"confusing-browser-globals": "^1.0.9",
|
"confusing-browser-globals": "^1.0.9",
|
||||||
@@ -1340,7 +1340,7 @@
|
|||||||
"eslint-plugin-prettier": "4.0.0",
|
"eslint-plugin-prettier": "4.0.0",
|
||||||
"eslint-plugin-tsdoc": "^0.2.5",
|
"eslint-plugin-tsdoc": "^0.2.5",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"typescript": "^3.9.3"
|
"typescript": ">=3.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@xrplf/prettier-config": {
|
"node_modules/@xrplf/prettier-config": {
|
||||||
@@ -9981,9 +9981,9 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@xrplf/eslint-config": {
|
"@xrplf/eslint-config": {
|
||||||
"version": "1.2.2",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@xrplf/eslint-config/-/eslint-config-1.3.0.tgz",
|
||||||
"integrity": "sha512-mZ4m0Q/3eE5dKbC1z875yrrcq9/X6O1g8uxqJSYSoWPY1AlwvfqSLm6qoe4CJ5v8eaOGFNWQGWF6a4na8pYoDw==",
|
"integrity": "sha512-Zk3ZgzHxj8vozA87LF6Gv4B9v8KXH51Q4oW2sG4fCFHsqiSWVG+n4ZPr7DhTqPNhaxV21yL3OqsT7G6FgD1wXg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"confusing-browser-globals": "^1.0.9",
|
"confusing-browser-globals": "^1.0.9",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"@types/puppeteer": "5.4.4",
|
"@types/puppeteer": "5.4.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
||||||
"@typescript-eslint/parser": "^4.0.0",
|
"@typescript-eslint/parser": "^4.0.0",
|
||||||
"@xrplf/eslint-config": "^1.2.2",
|
"@xrplf/eslint-config": "^1.3",
|
||||||
"@xrplf/prettier-config": "^1.2.0",
|
"@xrplf/prettier-config": "^1.2.0",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"buffer": "^6.0.2",
|
"buffer": "^6.0.2",
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import { classicAddressToXAddress } from 'ripple-address-codec'
|
import { classicAddressToXAddress } from 'ripple-address-codec'
|
||||||
import { deriveKeypair, deriveAddress } from 'ripple-keypairs'
|
import { deriveKeypair, deriveAddress } from 'ripple-keypairs'
|
||||||
|
|
||||||
function deriveXAddress(options: {
|
interface DeriveOptions {
|
||||||
publicKey: string
|
publicKey: string
|
||||||
tag: number | false
|
tag: number | false
|
||||||
test: boolean
|
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)
|
const classicAddress = deriveAddress(options.publicKey)
|
||||||
return classicAddressToXAddress(classicAddress, options.tag, options.test)
|
return classicAddressToXAddress(classicAddress, options.tag, options.test)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { classicAddressToXAddress } from 'ripple-address-codec'
|
import { classicAddressToXAddress } from 'ripple-address-codec'
|
||||||
import keypairs from 'ripple-keypairs'
|
import keypairs from 'ripple-keypairs'
|
||||||
|
|
||||||
import { errors } from '../common'
|
|
||||||
import ECDSA from '../common/ecdsa'
|
import ECDSA from '../common/ecdsa'
|
||||||
|
import { UnexpectedError } from '../common/errors'
|
||||||
|
|
||||||
export interface GeneratedAddress {
|
export interface GeneratedAddress {
|
||||||
xAddress: string
|
xAddress: string
|
||||||
classicAddress?: string
|
classicAddress?: string
|
||||||
address?: string // @deprecated Use `classicAddress` instead.
|
|
||||||
secret: string
|
secret: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +26,14 @@ export interface GenerateAddressOptions {
|
|||||||
includeClassicAddress?: boolean
|
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(
|
function generateXAddress(
|
||||||
options: GenerateAddressOptions = {},
|
options: GenerateAddressOptions = {},
|
||||||
): GeneratedAddress {
|
): GeneratedAddress {
|
||||||
@@ -44,7 +50,7 @@ function generateXAddress(
|
|||||||
const secret = keypairs.generateSeed(generateSeedOptions)
|
const secret = keypairs.generateSeed(generateSeedOptions)
|
||||||
const keypair = keypairs.deriveKeypair(secret)
|
const keypair = keypairs.deriveKeypair(secret)
|
||||||
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
|
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
|
||||||
const returnValue: any = {
|
const returnValue: GeneratedAddress = {
|
||||||
xAddress: classicAddressToXAddress(
|
xAddress: classicAddressToXAddress(
|
||||||
classicAddress,
|
classicAddress,
|
||||||
false,
|
false,
|
||||||
@@ -54,11 +60,14 @@ function generateXAddress(
|
|||||||
}
|
}
|
||||||
if (options.includeClassicAddress) {
|
if (options.includeClassicAddress) {
|
||||||
returnValue.classicAddress = classicAddress
|
returnValue.classicAddress = classicAddress
|
||||||
returnValue.address = classicAddress
|
|
||||||
}
|
}
|
||||||
return returnValue
|
return returnValue
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new errors.UnexpectedError(error.message)
|
if (error instanceof Error) {
|
||||||
|
throw new UnexpectedError(error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -12,29 +12,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
enum HashPrefix {
|
enum HashPrefix {
|
||||||
// transaction plus signature to give transaction ID
|
// transaction plus signature to give transaction ID 'TXN'
|
||||||
TRANSACTION_ID = 0x54584e00, // 'TXN'
|
TRANSACTION_ID = 0x54584e00,
|
||||||
|
|
||||||
// transaction plus metadata
|
// transaction plus metadata 'TND'
|
||||||
TRANSACTION_NODE = 0x534e4400, // 'TND'
|
TRANSACTION_NODE = 0x534e4400,
|
||||||
|
|
||||||
// inner node in tree
|
// inner node in tree 'MIN'
|
||||||
INNER_NODE = 0x4d494e00, // 'MIN'
|
INNER_NODE = 0x4d494e00,
|
||||||
|
|
||||||
// leaf node in tree
|
// leaf node in tree 'MLN'
|
||||||
LEAF_NODE = 0x4d4c4e00, // 'MLN'
|
LEAF_NODE = 0x4d4c4e00,
|
||||||
|
|
||||||
// inner transaction to sign
|
// inner transaction to sign 'STX'
|
||||||
TRANSACTION_SIGN = 0x53545800, // 'STX'
|
TRANSACTION_SIGN = 0x53545800,
|
||||||
|
|
||||||
// inner transaction to sign (TESTNET)
|
// inner transaction to sign (TESTNET) 'stx'
|
||||||
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'
|
TRANSACTION_SIGN_TESTNET = 0x73747800,
|
||||||
|
|
||||||
// inner transaction to multisign
|
// inner transaction to multisign 'SMT'
|
||||||
TRANSACTION_MULTISIGN = 0x534d5400, // 'SMT'
|
TRANSACTION_MULTISIGN = 0x534d5400,
|
||||||
|
|
||||||
// ledger
|
// ledger 'LWR'
|
||||||
LEDGER = 0x4c575200, // 'LWR'
|
LEDGER = 0x4c575200,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HashPrefix
|
export default HashPrefix
|
||||||
|
|||||||
@@ -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 BigNumber from 'bignumber.js'
|
||||||
import { decodeAccountID } from 'ripple-address-codec'
|
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 HashPrefix from './hashPrefix'
|
||||||
|
import computeLedgerHash, {
|
||||||
|
computeLedgerHeaderHash,
|
||||||
|
computeSignedTransactionHash,
|
||||||
|
computeTransactionTreeHash,
|
||||||
|
computeStateTreeHash,
|
||||||
|
} from './ledgerHash'
|
||||||
import ledgerSpaces from './ledgerSpaces'
|
import ledgerSpaces from './ledgerSpaces'
|
||||||
import sha512Half from './sha512Half'
|
import sha512Half from './sha512Half'
|
||||||
import { SHAMap, NodeType } from './shamap'
|
|
||||||
|
|
||||||
const BITS_IN_HEX = 16
|
const HEX = 16
|
||||||
const BYTE_LENGTH = 4
|
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 {
|
function addressToHex(address: string): string {
|
||||||
return Buffer.from(decodeAccountID(address)).toString('hex')
|
return Buffer.from(decodeAccountID(address)).toString('hex')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
function currencyToHex(currency: string): string {
|
||||||
if (currency.length === 3) {
|
if (currency.length !== 3) {
|
||||||
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
|
return currency
|
||||||
bytes[12] = currency.charCodeAt(0) & 0xff
|
|
||||||
bytes[13] = currency.charCodeAt(1) & 0xff
|
|
||||||
bytes[14] = currency.charCodeAt(2) & 0xff
|
|
||||||
return bytesToHex(bytes)
|
|
||||||
}
|
|
||||||
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 (
|
const bytes = Array(20).fill(0)
|
||||||
txObject.TxnSignature === undefined &&
|
bytes[12] = currency.charCodeAt(0) & MASK
|
||||||
(txObject.Signers === undefined ||
|
bytes[13] = currency.charCodeAt(1) & MASK
|
||||||
txObject.Signers[0].Signer.TxnSignature === undefined)
|
bytes[14] = currency.charCodeAt(2) & MASK
|
||||||
) {
|
return Buffer.from(bytes).toString('hex')
|
||||||
throw new ValidationError('The transaction must be signed to hash it.')
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
|
|
||||||
return sha512Half(prefix.concat(txBlob))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,19 +49,19 @@ export function computeSignedTransactionHash(tx: Transaction | string): string {
|
|||||||
* @returns The hash to sign.
|
* @returns The hash to sign.
|
||||||
*/
|
*/
|
||||||
export function computeBinaryTransactionSigningHash(txBlobHex: string): string {
|
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)
|
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.
|
* All objects in a ledger's state tree have a unique Index.
|
||||||
* The Account Root index is derived by hashing the
|
* The AccountRoot Ledger Object Index is derived by hashing the
|
||||||
* address with a namespace identifier. This ensures every
|
* 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.
|
* @param address - The classic account address.
|
||||||
* @returns The Ledger Object Index for the account.
|
* @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 RippleState space key (0x0053)
|
||||||
* * The AccountID of the owner of the SignerList
|
* * The AccountID of the owner of the SignerList
|
||||||
* * The SignerListID (currently always 0).
|
* * 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).
|
* @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 {
|
export function computeSignerListIndex(address: string): string {
|
||||||
return sha512Half(
|
return sha512Half(
|
||||||
`${ledgerSpaceHex('signerList') + addressToHex(address)}00000000`,
|
`${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 Offer space key (0x006F)
|
||||||
* * The AccountID of the account placing the offer
|
* * The AccountID of the account placing the offer
|
||||||
* * The Sequence number of the OfferCreate transaction that created 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 address - The classic account address of the SignerList owner (starting with r).
|
||||||
* @param sequence
|
* @param sequence - Sequence of the Offer.
|
||||||
* @returns The index of the account's Offer object.
|
* @returns The Index of the account's Offer object.
|
||||||
*/
|
*/
|
||||||
export function computeOfferIndex(address: string, sequence: number): string {
|
export function computeOfferIndex(address: string, sequence: number): string {
|
||||||
const prefix = `00${intToHex(ledgerSpaces.offer.charCodeAt(0), 1)}`
|
const hexPrefix = ledgerSpaces.offer
|
||||||
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
|
.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(
|
export function computeTrustlineHash(
|
||||||
address1: string,
|
address1: string,
|
||||||
address2: string,
|
address2: string,
|
||||||
@@ -189,56 +141,29 @@ export function computeTrustlineHash(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeTransactionTreeHash(transactions: any[]): string {
|
/**
|
||||||
const shamap = new SHAMap()
|
* Compute the Hash of an Escrow LedgerEntry.
|
||||||
|
*
|
||||||
transactions.forEach((txJSON) => {
|
* @param address - Address of the Escrow.
|
||||||
const txBlobHex = encode(txJSON)
|
* @param sequence - OfferSequence of the Escrow.
|
||||||
const metaHex = encode(txJSON.metaData)
|
* @returns The hash of the Escrow LedgerEntry.
|
||||||
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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function computeEscrowHash(address: string, sequence: number): string {
|
export function computeEscrowHash(address: string, sequence: number): string {
|
||||||
return sha512Half(
|
return sha512Half(
|
||||||
ledgerSpaceHex('escrow') +
|
ledgerSpaceHex('escrow') +
|
||||||
addressToHex(address) +
|
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(
|
export function computePaymentChannelHash(
|
||||||
address: string,
|
address: string,
|
||||||
dstAddress: string,
|
dstAddress: string,
|
||||||
@@ -248,6 +173,14 @@ export function computePaymentChannelHash(
|
|||||||
ledgerSpaceHex('paychan') +
|
ledgerSpaceHex('paychan') +
|
||||||
addressToHex(address) +
|
addressToHex(address) +
|
||||||
addressToHex(dstAddress) +
|
addressToHex(dstAddress) +
|
||||||
intToHex(sequence, BYTE_LENGTH),
|
sequence.toString(HEX).padStart(BYTE_LENGTH * 2, '0'),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
computeLedgerHeaderHash,
|
||||||
|
computeSignedTransactionHash,
|
||||||
|
computeLedgerHash,
|
||||||
|
computeStateTreeHash,
|
||||||
|
computeTransactionTreeHash,
|
||||||
|
}
|
||||||
|
|||||||
230
src/utils/hashes/ledgerHash.ts
Normal file
230
src/utils/hashes/ledgerHash.ts
Normal 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
|
||||||
@@ -8,14 +8,17 @@
|
|||||||
*
|
*
|
||||||
* See [LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100).
|
* See [LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100).
|
||||||
*/
|
*/
|
||||||
export default {
|
const ledgerSpaces = {
|
||||||
account: 'a',
|
account: 'a',
|
||||||
dirNode: 'd',
|
dirNode: 'd',
|
||||||
generatorMap: 'g',
|
generatorMap: 'g',
|
||||||
rippleState: 'r',
|
rippleState: 'r',
|
||||||
offer: 'o', // Entry for an offer.
|
// Entry for an offer.
|
||||||
ownerDir: 'O', // Directory of things owned by an account.
|
offer: 'o',
|
||||||
bookDir: 'B', // Directory of order books.
|
// Directory of things owned by an account.
|
||||||
|
ownerDir: 'O',
|
||||||
|
// Directory of order books.
|
||||||
|
bookDir: 'B',
|
||||||
contract: 'c',
|
contract: 'c',
|
||||||
skipList: 's',
|
skipList: 's',
|
||||||
escrow: 'u',
|
escrow: 'u',
|
||||||
@@ -27,3 +30,5 @@ export default {
|
|||||||
check: 'C',
|
check: 'C',
|
||||||
depositPreauth: 'p',
|
depositPreauth: 'p',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ledgerSpaces
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import { createHash } from 'crypto'
|
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')
|
return createHash('sha512')
|
||||||
.update(Buffer.from(hex, 'hex'))
|
.update(Buffer.from(hex, 'hex'))
|
||||||
.digest('hex')
|
.digest('hex')
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.slice(0, 64)
|
.slice(0, HASH_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default sha512Half
|
export default sha512Half
|
||||||
|
|||||||
37
src/utils/hashes/shaMap/index.ts
Normal file
37
src/utils/hashes/shaMap/index.ts
Normal 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
|
||||||
123
src/utils/hashes/shaMap/innerNode.ts
Normal file
123
src/utils/hashes/shaMap/innerNode.ts
Normal 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
|
||||||
67
src/utils/hashes/shaMap/leafNode.ts
Normal file
67
src/utils/hashes/shaMap/leafNode.ts
Normal 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
|
||||||
14
src/utils/hashes/shaMap/node.ts
Normal file
14
src/utils/hashes/shaMap/node.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import BigNumber from 'bignumber.js'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { xAddressToClassicAddress } from 'ripple-address-codec'
|
import { xAddressToClassicAddress } from 'ripple-address-codec'
|
||||||
|
|
||||||
import { ValidationError } from '../common/errors'
|
import { ValidationError } from '../common/errors'
|
||||||
@@ -17,114 +15,37 @@ import {
|
|||||||
computeTransactionTreeHash,
|
computeTransactionTreeHash,
|
||||||
computeStateTreeHash,
|
computeStateTreeHash,
|
||||||
computeLedgerHash,
|
computeLedgerHash,
|
||||||
|
computeLedgerHeaderHash,
|
||||||
computeEscrowHash,
|
computeEscrowHash,
|
||||||
computePaymentChannelHash,
|
computePaymentChannelHash,
|
||||||
} from './hashes'
|
} from './hashes'
|
||||||
import computeLedgerHeaderHash from './ledgerHash'
|
|
||||||
import signPaymentChannelClaim from './signPaymentChannelClaim'
|
import signPaymentChannelClaim from './signPaymentChannelClaim'
|
||||||
|
import { rippleTimeToISOTime, ISOTimeToRippleTime } from './timeConversion'
|
||||||
import verifyPaymentChannelClaim from './verifyPaymentChannelClaim'
|
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 {
|
function isValidSecret(secret: string): boolean {
|
||||||
try {
|
try {
|
||||||
deriveKeypair(secret)
|
deriveKeypair(secret)
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dropsToXrp(drops: BigNumber.Value): string {
|
/**
|
||||||
if (typeof drops === 'string') {
|
* TODO: Remove/rename this function.
|
||||||
if (!/^-?[0-9]*\.?[0-9]*$/.exec(drops)) {
|
*
|
||||||
throw new ValidationError(
|
* @param amount - Convert an Amount in.
|
||||||
`dropsToXrp: invalid value '${drops}',` +
|
* @returns Amount without X-Address issuer.
|
||||||
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`,
|
* @throws When issuer X-Address includes a tag.
|
||||||
)
|
*/
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toRippledAmount(amount: RippledAmount): RippledAmount {
|
function toRippledAmount(amount: RippledAmount): RippledAmount {
|
||||||
if (typeof amount === 'string') {
|
if (typeof amount === 'string') {
|
||||||
return amount
|
return amount
|
||||||
@@ -137,14 +58,14 @@ function toRippledAmount(amount: RippledAmount): RippledAmount {
|
|||||||
return amount.value
|
return amount.value
|
||||||
}
|
}
|
||||||
|
|
||||||
let issuer = amount.counterparty || amount.issuer
|
let issuer = amount.counterparty ?? amount.issuer
|
||||||
let tag: number | false = false
|
let tag: number | false = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (issuer) {
|
if (issuer) {
|
||||||
;({ classicAddress: issuer, tag } = xAddressToClassicAddress(issuer))
|
;({ classicAddress: issuer, tag } = xAddressToClassicAddress(issuer))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (_e) {
|
||||||
/* not an X-address */
|
/* 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).
|
* Removes undefined values from an object.
|
||||||
* @returns Milliseconds since unix epoch.
|
*
|
||||||
|
* @param obj - Object to remove undefined values from.
|
||||||
|
* @returns The same object, but without undefined values.
|
||||||
*/
|
*/
|
||||||
function rippleToUnixTimestamp(rpepoch: number): number {
|
function removeUndefined<T extends Record<string, unknown>>(obj: T): T {
|
||||||
return (rpepoch + 0x386d4380) * 1000
|
const newObj = { ...obj }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Object.entries(obj).forEach(([key, value]) => {
|
||||||
* @param timestamp - (ms since unix epoch).
|
if (value == null) {
|
||||||
* @returns Seconds since Ripple Epoch (1/1/2000 GMT).
|
/* eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- Deletes undefined values. */
|
||||||
*/
|
delete newObj[key]
|
||||||
function unixToRippleTimestamp(timestamp: number): number {
|
}
|
||||||
return Math.round(timestamp / 1000) - 0x386d4380
|
})
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return newObj
|
||||||
* @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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertStringToHex(string: string): string {
|
function convertStringToHex(string: string): string {
|
||||||
@@ -218,11 +104,9 @@ function convertStringToHex(string: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
computeLedgerHeaderHash,
|
|
||||||
dropsToXrp,
|
dropsToXrp,
|
||||||
xrpToDrops,
|
xrpToDrops,
|
||||||
toRippledAmount,
|
toRippledAmount,
|
||||||
convertKeysFromSnakeCaseToCamelCase,
|
|
||||||
removeUndefined,
|
removeUndefined,
|
||||||
rippleTimeToISOTime,
|
rippleTimeToISOTime,
|
||||||
ISOTimeToRippleTime,
|
ISOTimeToRippleTime,
|
||||||
@@ -236,6 +120,7 @@ export {
|
|||||||
computeTransactionTreeHash,
|
computeTransactionTreeHash,
|
||||||
computeStateTreeHash,
|
computeStateTreeHash,
|
||||||
computeLedgerHash,
|
computeLedgerHash,
|
||||||
|
computeLedgerHeaderHash,
|
||||||
computeEscrowHash,
|
computeEscrowHash,
|
||||||
computePaymentChannelHash,
|
computePaymentChannelHash,
|
||||||
generateXAddress,
|
generateXAddress,
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,8 +1,16 @@
|
|||||||
import binary from 'ripple-binary-codec'
|
import binary from 'ripple-binary-codec'
|
||||||
import keypairs from 'ripple-keypairs'
|
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(
|
function signPaymentChannelClaim(
|
||||||
channel: string,
|
channel: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
|
|||||||
46
src/utils/timeConversion.ts
Normal file
46
src/utils/timeConversion.ts
Normal 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,
|
||||||
|
}
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
import binary from 'ripple-binary-codec'
|
import binary from 'ripple-binary-codec'
|
||||||
import keypairs from 'ripple-keypairs'
|
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(
|
function verifyPaymentChannelClaim(
|
||||||
channel: string,
|
channel: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
|
|||||||
22
test/fixtures/requests/computeLedgerHash.json
vendored
22
test/fixtures/requests/computeLedgerHash.json
vendored
@@ -1,11 +1,15 @@
|
|||||||
{
|
{
|
||||||
"stateHash": "D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44",
|
"account_hash": "D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44",
|
||||||
"closeTime": "2015-08-12T01:01:10.000Z",
|
"close_time": 492656470,
|
||||||
"parentCloseTime": "2015-08-12T01:01:00.000Z",
|
"parent_close_time": 492656460,
|
||||||
"closeFlags": 0,
|
"close_flags": 0,
|
||||||
"closeTimeResolution": 10,
|
"ledger_index": "15202439",
|
||||||
"ledgerVersion": 15202439,
|
"close_time_human": "2015-Aug-12 01:01:10.000000000 UTC",
|
||||||
"parentLedgerHash": "12724A65B030C15A1573AA28B1BBB5DF3DA4589AA3623675A31CAE69B23B1C4E",
|
"close_time_resolution": 10,
|
||||||
"totalDrops": "99998831688050493",
|
"closed": true,
|
||||||
"transactionHash": "325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C8"
|
"hash": "F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349",
|
||||||
|
"ledger_hash": "F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349",
|
||||||
|
"parent_hash": "12724A65B030C15A1573AA28B1BBB5DF3DA4589AA3623675A31CAE69B23B1C4E",
|
||||||
|
"total_coins": "99998831688050493",
|
||||||
|
"transaction_hash": "325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C8"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"hash": "F8F337DEE5D5B238A10AF4A4D56926BA26C83EE7AF5A5A6474340C56F9252DF3",
|
"TransactionType": "Payment",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Flags": 2147483648,
|
||||||
"ledger_index": 15202439,
|
"Sequence": 1608,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202446,
|
||||||
"TransactionType": "Payment",
|
"Amount": "120000000",
|
||||||
"Flags": 2147483648,
|
"Fee": "15000",
|
||||||
"Sequence": 1608,
|
"SigningPubKey": "03BC0973F997BC6384BE455B163519A3E96BC2D725C37F7172D5FED5DD38E2A357",
|
||||||
"LastLedgerSequence": 15202446,
|
"TxnSignature": "3045022100D80A1802B00AEEF9FDFDE594B0D568217A312D54E6337B8519C0D699841EFB96022067F6913B13D0EC2354C5A67CE0A41AE4181A09CD08A1BB0638D128D357961006",
|
||||||
"Amount": "120000000",
|
"Account": "rDPL68aNpdfp9h59R4QT5R6B1Z2W9oRc51",
|
||||||
"Fee": "15000",
|
"Destination": "rE4S4Xw8euysJ3mt7gmK8EhhYEwmALpb3R",
|
||||||
"SigningPubKey": "03BC0973F997BC6384BE455B163519A3E96BC2D725C37F7172D5FED5DD38E2A357",
|
"metaData": {
|
||||||
"TxnSignature": "3045022100D80A1802B00AEEF9FDFDE594B0D568217A312D54E6337B8519C0D699841EFB96022067F6913B13D0EC2354C5A67CE0A41AE4181A09CD08A1BB0638D128D357961006",
|
|
||||||
"Account": "rDPL68aNpdfp9h59R4QT5R6B1Z2W9oRc51",
|
|
||||||
"Destination": "rE4S4Xw8euysJ3mt7gmK8EhhYEwmALpb3R"
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"TransactionIndex": 6,
|
"TransactionIndex": 6,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
@@ -60,22 +55,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hash": "F8D5DE632B1D8B64E577C46912CCE483D6DF4FD4E2CF4A3D586A099DE3B27021",
|
"TransactionType": "Payment",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Flags": 2147483648,
|
||||||
"ledger_index": 15202439,
|
"Sequence": 18874,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202446,
|
||||||
"TransactionType": "Payment",
|
"Amount": "120000000",
|
||||||
"Flags": 2147483648,
|
"Fee": "15000",
|
||||||
"Sequence": 18874,
|
"SigningPubKey": "035D097E75D4B35345CEB30F9B1D18CB81165FE6ADD02481AA5B02B5F9C8107EE1",
|
||||||
"LastLedgerSequence": 15202446,
|
"TxnSignature": "304402203D80E8BC71908AB345948AB71FB7B8DE239DD79636D96D3C5BDA2B2F192A5EEA0220686413D69BF0D813FC61DABD437AEFAAE69925D3E10FCD5B2C4D90B5AF7B883D",
|
||||||
"Amount": "120000000",
|
"Account": "rnHScgV6wSP9sR25uYWiMo3QYNA5ybQ7cH",
|
||||||
"Fee": "15000",
|
"Destination": "rwnnfHDaEAwXaVji52cWWizbHVMs2Cz5K9",
|
||||||
"SigningPubKey": "035D097E75D4B35345CEB30F9B1D18CB81165FE6ADD02481AA5B02B5F9C8107EE1",
|
"metaData": {
|
||||||
"TxnSignature": "304402203D80E8BC71908AB345948AB71FB7B8DE239DD79636D96D3C5BDA2B2F192A5EEA0220686413D69BF0D813FC61DABD437AEFAAE69925D3E10FCD5B2C4D90B5AF7B883D",
|
|
||||||
"Account": "rnHScgV6wSP9sR25uYWiMo3QYNA5ybQ7cH",
|
|
||||||
"Destination": "rwnnfHDaEAwXaVji52cWWizbHVMs2Cz5K9"
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"TransactionIndex": 5,
|
"TransactionIndex": 5,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
@@ -120,22 +110,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hash": "E9004490A92413E92DACD621AC73FD434A8950C350F7572FFEAF4D6AAF8FC288",
|
"TransactionType": "Payment",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Flags": 2147483648,
|
||||||
"ledger_index": 15202439,
|
"Sequence": 1615,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202446,
|
||||||
"TransactionType": "Payment",
|
"Amount": "400000000",
|
||||||
"Flags": 2147483648,
|
"Fee": "15000",
|
||||||
"Sequence": 1615,
|
"SigningPubKey": "03ACFAA11628C558AB5E7FA64705F442BDAABA6E9D318B30E010BC87CDEA8D1D7D",
|
||||||
"LastLedgerSequence": 15202446,
|
"TxnSignature": "3045022100A3530C2E983FB05DFF27172C649494291F7BEBA2E6A59EEAF945CB9728D1DB5E022015BCA0E9D69760224DD7C2B68F3BC1F239D89C3397161AA3901C2E04EE31C18F",
|
||||||
"Amount": "400000000",
|
"Account": "razcSDpwds1aTeqDphqzBr7ay1ZELYAWTm",
|
||||||
"Fee": "15000",
|
"Destination": "rhuqJAE2UfhGCvkR7Ve35bvm39JmRvFML4",
|
||||||
"SigningPubKey": "03ACFAA11628C558AB5E7FA64705F442BDAABA6E9D318B30E010BC87CDEA8D1D7D",
|
"metaData": {
|
||||||
"TxnSignature": "3045022100A3530C2E983FB05DFF27172C649494291F7BEBA2E6A59EEAF945CB9728D1DB5E022015BCA0E9D69760224DD7C2B68F3BC1F239D89C3397161AA3901C2E04EE31C18F",
|
|
||||||
"Account": "razcSDpwds1aTeqDphqzBr7ay1ZELYAWTm",
|
|
||||||
"Destination": "rhuqJAE2UfhGCvkR7Ve35bvm39JmRvFML4"
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"TransactionIndex": 4,
|
"TransactionIndex": 4,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
@@ -180,22 +165,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hash": "D44BFF924D23211B82B8F604AF6D92F260F8DD13103A96F03E48825C4A978FD6",
|
"TransactionType": "Payment",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Flags": 2147483648,
|
||||||
"ledger_index": 15202439,
|
"Sequence": 1674,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202446,
|
||||||
"TransactionType": "Payment",
|
"Amount": "800000000",
|
||||||
"Flags": 2147483648,
|
"Fee": "15000",
|
||||||
"Sequence": 1674,
|
"SigningPubKey": "028F28D78FDA74222F4008F012247DF3BBD42B90CE4CFD87E29598196108E91B52",
|
||||||
"LastLedgerSequence": 15202446,
|
"TxnSignature": "3044022065A003194D91E774D180BE47D4E086BB2624BC8F6DB7C655E135D5C6C03BBC7C02205DC961C2B7A06D701B29C2116ACF6F84CC84205FF44411576C15507852ECC31C",
|
||||||
"Amount": "800000000",
|
"Account": "rQGLp9nChtWkdgcHjj6McvJithN2S2HJsP",
|
||||||
"Fee": "15000",
|
"Destination": "rEUubanepAAugnNJY1gxEZLDnk9W5NCoFU",
|
||||||
"SigningPubKey": "028F28D78FDA74222F4008F012247DF3BBD42B90CE4CFD87E29598196108E91B52",
|
"metaData": {
|
||||||
"TxnSignature": "3044022065A003194D91E774D180BE47D4E086BB2624BC8F6DB7C655E135D5C6C03BBC7C02205DC961C2B7A06D701B29C2116ACF6F84CC84205FF44411576C15507852ECC31C",
|
|
||||||
"Account": "rQGLp9nChtWkdgcHjj6McvJithN2S2HJsP",
|
|
||||||
"Destination": "rEUubanepAAugnNJY1gxEZLDnk9W5NCoFU"
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"TransactionIndex": 3,
|
"TransactionIndex": 3,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
@@ -240,39 +220,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hash": "C978D915BFB17687335CBFC4B207D9E7213BCEE35B468C2EEE016CDCE4EDB6E4",
|
"TransactionType": "OfferCreate",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Sequence": 289444,
|
||||||
"ledger_index": 15202439,
|
"OfferSequence": 289443,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202441,
|
||||||
"TransactionType": "OfferCreate",
|
"TakerPays": {
|
||||||
"Sequence": 289444,
|
"value": "19.99999999991",
|
||||||
"OfferSequence": 289443,
|
"currency": "EUR",
|
||||||
"LastLedgerSequence": 15202441,
|
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||||
"TakerPays": {
|
|
||||||
"value": "19.99999999991",
|
|
||||||
"currency": "EUR",
|
|
||||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
|
||||||
},
|
|
||||||
"TakerGets": {
|
|
||||||
"value": "20.88367500010602",
|
|
||||||
"currency": "USD",
|
|
||||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
|
||||||
},
|
|
||||||
"Fee": "10000",
|
|
||||||
"SigningPubKey": "024D129D4F5A12D4C5A9E9D1E4AC447BBE3496F182FAE82F7709C7EB9F12DBC697",
|
|
||||||
"TxnSignature": "3044022041EBE6B06BA493867F4FFBD72E5D6253F97306E1E82DABDF9649E15B1151B59F0220539C589F40174471C067FDC761A2B791F36F1A3C322734B43DB16880E489BD81",
|
|
||||||
"Account": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq",
|
|
||||||
"Memos": [
|
|
||||||
{
|
|
||||||
"Memo": {
|
|
||||||
"MemoType": "6F666665725F636F6D6D656E74",
|
|
||||||
"MemoData": "72655F6575722368656467655F726970706C65",
|
|
||||||
"parsed_memo_type": "offer_comment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"meta": {
|
"TakerGets": {
|
||||||
|
"value": "20.88367500010602",
|
||||||
|
"currency": "USD",
|
||||||
|
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||||
|
},
|
||||||
|
"Fee": "10000",
|
||||||
|
"SigningPubKey": "024D129D4F5A12D4C5A9E9D1E4AC447BBE3496F182FAE82F7709C7EB9F12DBC697",
|
||||||
|
"TxnSignature": "3044022041EBE6B06BA493867F4FFBD72E5D6253F97306E1E82DABDF9649E15B1151B59F0220539C589F40174471C067FDC761A2B791F36F1A3C322734B43DB16880E489BD81",
|
||||||
|
"Account": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq",
|
||||||
|
"Memos": [
|
||||||
|
{
|
||||||
|
"Memo": {
|
||||||
|
"MemoType": "6F666665725F636F6D6D656E74",
|
||||||
|
"MemoData": "72655F6575722368656467655F726970706C65",
|
||||||
|
"parsed_memo_type": "offer_comment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metaData": {
|
||||||
"TransactionIndex": 2,
|
"TransactionIndex": 2,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
@@ -372,20 +347,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hash": "31B34FD7C90CDC6CF680A814DEBC6F616C69275C0E99711F904DE088A8ED4B28",
|
"TransactionType": "AccountSet",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Flags": 2147483648,
|
||||||
"ledger_index": 15202439,
|
"Sequence": 387262,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202440,
|
||||||
"TransactionType": "AccountSet",
|
"Fee": "10500",
|
||||||
"Flags": 2147483648,
|
"SigningPubKey": "027DFE042DC2BD07D2E88DD526A5FBF816C831C25CA0BB62A3BF320A3B2BA6DB5C",
|
||||||
"Sequence": 387262,
|
"TxnSignature": "30440220572D89688D9F9DB9874CDDDD3EBDCB5808A836982864C81F185FBC54FAD1A7B902202E09AAA6D65EECC9ACDEA7F70D8D2EE024152C7B288FA9E42C427260CF922F58",
|
||||||
"LastLedgerSequence": 15202440,
|
"Account": "rn6uAt46Xi6uxA2dRCtqaJyM3aaP6V9WWM",
|
||||||
"Fee": "10500",
|
"metaData": {
|
||||||
"SigningPubKey": "027DFE042DC2BD07D2E88DD526A5FBF816C831C25CA0BB62A3BF320A3B2BA6DB5C",
|
|
||||||
"TxnSignature": "30440220572D89688D9F9DB9874CDDDD3EBDCB5808A836982864C81F185FBC54FAD1A7B902202E09AAA6D65EECC9ACDEA7F70D8D2EE024152C7B288FA9E42C427260CF922F58",
|
|
||||||
"Account": "rn6uAt46Xi6uxA2dRCtqaJyM3aaP6V9WWM"
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"TransactionIndex": 1,
|
"TransactionIndex": 1,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
@@ -412,22 +382,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hash": "260BC2964FFE6D81CB25C152F8054FFB2CE6ED04FF89D8D0D0559BC14BEF0E46",
|
"TransactionType": "Payment",
|
||||||
"date": "2015-08-12T01:01:10+00:00",
|
"Flags": 2147483648,
|
||||||
"ledger_index": 15202439,
|
"Sequence": 1673,
|
||||||
"tx": {
|
"LastLedgerSequence": 15202446,
|
||||||
"TransactionType": "Payment",
|
"Amount": "1700000000",
|
||||||
"Flags": 2147483648,
|
"Fee": "15000",
|
||||||
"Sequence": 1673,
|
"SigningPubKey": "02C26CF5D395A1CB352BE10D5AAB73FE27FC0AFAE0BD6121E55D097EBDCF394E11",
|
||||||
"LastLedgerSequence": 15202446,
|
"TxnSignature": "304402204190B6DC7D14B1CC8DDAA87F1C01FEDA6D67D598D65E1AA19D4ADE937ED14B720220662EE404438F415AD3335B9FBA1A4C2A5F72AA387740D8A011A8C53346481B1D",
|
||||||
"Amount": "1700000000",
|
"Account": "rEE77T1E5vEFcEB9zM92jBD3rPs3kPdS1j",
|
||||||
"Fee": "15000",
|
"Destination": "r3AsrDRMNYaKNCofo9a5Us7R66RAzTigiU",
|
||||||
"SigningPubKey": "02C26CF5D395A1CB352BE10D5AAB73FE27FC0AFAE0BD6121E55D097EBDCF394E11",
|
"metaData": {
|
||||||
"TxnSignature": "304402204190B6DC7D14B1CC8DDAA87F1C01FEDA6D67D598D65E1AA19D4ADE937ED14B720220662EE404438F415AD3335B9FBA1A4C2A5F72AA387740D8A011A8C53346481B1D",
|
|
||||||
"Account": "rEE77T1E5vEFcEB9zM92jBD3rPs3kPdS1j",
|
|
||||||
"Destination": "r3AsrDRMNYaKNCofo9a5Us7R66RAzTigiU"
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"TransactionIndex": 0,
|
"TransactionIndex": 0,
|
||||||
"AffectedNodes": [
|
"AffectedNodes": [
|
||||||
{
|
{
|
||||||
|
|||||||
2
test/fixtures/requests/index.ts
vendored
2
test/fixtures/requests/index.ts
vendored
@@ -178,7 +178,7 @@ const getOrderbook = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const computeLedgerHash = {
|
const computeLedgerHash = {
|
||||||
header: { ...header, rawTransactions: JSON.stringify(transactions) },
|
header,
|
||||||
transactions,
|
transactions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
test/fixtures/responses/generateAddress.json
vendored
1
test/fixtures/responses/generateAddress.json
vendored
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||||
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||||
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
|
||||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||||
}
|
}
|
||||||
|
|||||||
20
test/fixtures/responses/getLedger.json
vendored
20
test/fixtures/responses/getLedger.json
vendored
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"stateHash": "EC028EC32896D537ECCA18D18BEBE6AE99709FEFF9EF72DBD3A7819E918D8B96",
|
"account_hash": "EC028EC32896D537ECCA18D18BEBE6AE99709FEFF9EF72DBD3A7819E918D8B96",
|
||||||
"closeTime": "2014-09-24T21:21:50.000Z",
|
"close_time": "2014-09-24T21:21:50.000Z",
|
||||||
"closeTimeResolution": 10,
|
"close_time_resolution": 10,
|
||||||
"closeFlags": 0,
|
"close_flags": 0,
|
||||||
"ledgerHash": "0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A0F",
|
"ledger_hash": "0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A0F",
|
||||||
"ledgerVersion": 9038214,
|
"ledger_index": 9038214,
|
||||||
"parentLedgerHash": "4BB9CBE44C39DC67A1BE849C7467FE1A6D1F73949EA163C38A0121A15E04FFDE",
|
"parent_hash": "4BB9CBE44C39DC67A1BE849C7467FE1A6D1F73949EA163C38A0121A15E04FFDE",
|
||||||
"parentCloseTime": "2014-09-24T21:21:40.000Z",
|
"parent_close_time": "2014-09-24T21:21:40.000Z",
|
||||||
"totalDrops": "99999973964317514",
|
"total_coins": "99999973964317514",
|
||||||
"transactionHash": "ECB730839EB55B1B114D5D1AD2CD9A932C35BA9AB6D3A8C2F08935EAC2BAC239"
|
"transaction_hash": "ECB730839EB55B1B114D5D1AD2CD9A932C35BA9AB6D3A8C2F08935EAC2BAC239"
|
||||||
}
|
}
|
||||||
|
|||||||
25
test/fixtures/responses/getLedgerFull.json
vendored
25
test/fixtures/responses/getLedgerFull.json
vendored
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
import assert from 'assert'
|
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
|
const TYPE_TRANSACTION_NO_METADATA = NodeType.TRANSACTION_NO_METADATA
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,45 @@
|
|||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
|
|
||||||
import { ValidationError } from '../../src/common/errors'
|
import { ValidationError } from '../../src/common/errors'
|
||||||
import { computeLedgerHeaderHash } from '../../src/utils'
|
import { computeLedgerHash } from '../../src/utils'
|
||||||
import requests from '../fixtures/requests'
|
import requests from '../fixtures/requests'
|
||||||
import responses from '../fixtures/responses'
|
import responses from '../fixtures/responses'
|
||||||
import { assertResultMatch } from '../testUtils'
|
|
||||||
|
|
||||||
const { computeLedgerHash: REQUEST_FIXTURES } = requests
|
const { computeLedgerHash: REQUEST_FIXTURES } = requests
|
||||||
|
|
||||||
describe('computeLedgerHash', function () {
|
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 () {
|
it('given corrupt data - should fail', function () {
|
||||||
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
|
ledger.transactions[0] = JSON.parse(
|
||||||
ledger.transactions[0].rawTransaction =
|
'{"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}',
|
||||||
'{"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.parent_close_time = ledger.close_time
|
||||||
|
let hash
|
||||||
try {
|
try {
|
||||||
hash = computeLedgerHeaderHash(ledger, { computeTreeHashes: true })
|
hash = computeLedgerHash(ledger, { computeTreeHashes: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert(error instanceof ValidationError)
|
assert(error instanceof ValidationError)
|
||||||
assert.strictEqual(
|
if (error instanceof ValidationError) {
|
||||||
error.message,
|
assert.strictEqual(
|
||||||
'transactionHash in header does not match computed hash of transactions',
|
error.message,
|
||||||
)
|
'transactionHash in header does not match computed hash of transactions',
|
||||||
assert.deepStrictEqual(error.data, {
|
)
|
||||||
transactionHashInHeader:
|
assert.deepStrictEqual(error.data, {
|
||||||
'DB83BF807416C5B3499A73130F843CF615AB8E797D79FE7D330ADF1BFA93951A',
|
transactionHashInHeader:
|
||||||
computedHashOfTransactions:
|
'DB83BF807416C5B3499A73130F843CF615AB8E797D79FE7D330ADF1BFA93951A',
|
||||||
'EAA1ADF4D627339450F0E95EA88B7069186DD64230BAEBDCF3EEC4D616A9FC68',
|
computedHashOfTransactions:
|
||||||
})
|
'EAA1ADF4D627339450F0E95EA88B7069186DD64230BAEBDCF3EEC4D616A9FC68',
|
||||||
|
})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert(
|
assert(
|
||||||
@@ -38,77 +49,68 @@ describe('computeLedgerHash', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('given ledger without raw transactions - should throw', function () {
|
it('given ledger without raw transactions - should throw', function () {
|
||||||
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
|
delete ledger.transactions
|
||||||
delete ledger.transactions[0].rawTransaction
|
|
||||||
ledger.parentCloseTime = ledger.closeTime
|
ledger.parentCloseTime = ledger.closeTime
|
||||||
let hash: string
|
|
||||||
try {
|
assert.throws(
|
||||||
hash = computeLedgerHeaderHash(ledger, { computeTreeHashes: true })
|
() => computeLedgerHash(ledger, { computeTreeHashes: true }),
|
||||||
} catch (error) {
|
ValidationError,
|
||||||
assert(error instanceof ValidationError)
|
'transactions is missing from the ledger',
|
||||||
assert.strictEqual(error.message, 'ledger is missing raw transactions')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert(
|
|
||||||
false,
|
|
||||||
`Should throw ValidationError instead of producing hash: ${hash}`,
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('given ledger without state or transactions - only compute ledger hash', function () {
|
it('given ledger without state or transactions - only compute ledger hash', function () {
|
||||||
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
|
ledger.transactions[0] = JSON.parse(
|
||||||
assert.strictEqual(
|
'{"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.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.parentCloseTime = ledger.closeTime
|
|
||||||
const computeLedgerHash = computeLedgerHeaderHash
|
ledger.parent_close_time = ledger.close_time
|
||||||
function testCompute(ledgerToCompute, expectedError): void {
|
|
||||||
let hash = computeLedgerHash(ledgerToCompute)
|
function testCompute(ledgerToTest, expectedError): void {
|
||||||
|
const hash = computeLedgerHash(ledgerToTest)
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
hash,
|
hash,
|
||||||
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E',
|
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E',
|
||||||
)
|
)
|
||||||
|
|
||||||
// fail if required to compute tree hashes
|
// fail if required to compute tree hashes
|
||||||
try {
|
assert.throws(
|
||||||
hash = computeLedgerHash(ledgerToCompute, { computeTreeHashes: true })
|
() => computeLedgerHash(ledgerToTest, { computeTreeHashes: true }),
|
||||||
} catch (error) {
|
ValidationError,
|
||||||
assert(error instanceof ValidationError)
|
expectedError,
|
||||||
assert.strictEqual(error.message, expectedError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert(
|
|
||||||
false,
|
|
||||||
`Should throw ValidationError instead of producing hash: ${hash}`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactions = ledger.transactions
|
const transactions = ledger.transactions
|
||||||
delete ledger.transactions
|
delete ledger.transactions
|
||||||
testCompute(ledger, 'transactions property is missing from the ledger')
|
testCompute(ledger, 'transactions is missing from the ledger')
|
||||||
delete ledger.rawState
|
delete ledger.accountState
|
||||||
testCompute(ledger, 'transactions property is missing from the ledger')
|
testCompute(ledger, 'transactions is missing from the ledger')
|
||||||
ledger.transactions = transactions
|
ledger.transactions = transactions
|
||||||
testCompute(ledger, 'rawState property is missing from the ledger')
|
testCompute(ledger, 'accountState is missing from the ledger')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('wrong hash', function () {
|
it('wrong hash', function () {
|
||||||
const ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
|
|
||||||
assertResultMatch(ledger, responses.getLedger.full, 'getLedger')
|
|
||||||
const newLedger = {
|
const newLedger = {
|
||||||
...ledger,
|
...ledger,
|
||||||
parentCloseTime: ledger.closeTime,
|
parent_close_time: ledger.close_time,
|
||||||
stateHash:
|
account_hash:
|
||||||
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44',
|
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44',
|
||||||
}
|
}
|
||||||
assert.throws(() => {
|
|
||||||
computeLedgerHeaderHash(newLedger)
|
assert.throws(
|
||||||
}, /does not match computed hash of state/u)
|
() => {
|
||||||
|
computeLedgerHash(newLedger, { computeTreeHashes: true })
|
||||||
|
},
|
||||||
|
ValidationError,
|
||||||
|
'does not match computed hash of state',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('computeLedgerHash', function () {
|
it('computeLedgerHash', function () {
|
||||||
const header = REQUEST_FIXTURES.header
|
const header = REQUEST_FIXTURES.header
|
||||||
const ledgerHash = computeLedgerHeaderHash(header)
|
const ledgerHash = computeLedgerHash(header)
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
ledgerHash,
|
ledgerHash,
|
||||||
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
|
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
|
||||||
@@ -119,21 +121,27 @@ describe('computeLedgerHash', function () {
|
|||||||
const header = {
|
const header = {
|
||||||
...REQUEST_FIXTURES.header,
|
...REQUEST_FIXTURES.header,
|
||||||
transactionHash: undefined,
|
transactionHash: undefined,
|
||||||
rawTransactions: JSON.stringify(REQUEST_FIXTURES.transactions),
|
rawTransactions: REQUEST_FIXTURES.transactions,
|
||||||
}
|
}
|
||||||
const ledgerHash = computeLedgerHeaderHash(header)
|
const ledgerHash = computeLedgerHash(header)
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
ledgerHash,
|
ledgerHash,
|
||||||
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
|
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('computeLedgerHash - incorrent transaction_hash', function () {
|
it('computeLedgerHash - incorrect transaction_hash', function () {
|
||||||
const header = {
|
const header = {
|
||||||
...REQUEST_FIXTURES.header,
|
...REQUEST_FIXTURES.header,
|
||||||
transactionHash:
|
transaction_hash:
|
||||||
'325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C9',
|
'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',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ describe('generateAddress', function () {
|
|||||||
|
|
||||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -211,7 +210,6 @@ describe('generateAddress', function () {
|
|||||||
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
|
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
|
||||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user