mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-14 01:25:48 +00:00
Integrate ripple-hashes (#1039)
* Update TxParser and Mocha * Convert ripple-hashes to TypeScript and add into ripple-lib along with unit tests * Switch casing to camelCase * Document intToVuc * Convert Slots to numbers, add exception if number is outside 0-15 * Remove Shamap v2 support * Improve naming
This commit is contained in:
@@ -25,7 +25,6 @@
|
||||
"lodash": "^4.17.4",
|
||||
"ripple-address-codec": "^3.0.4",
|
||||
"ripple-binary-codec": "^0.2.4",
|
||||
"ripple-hashes": "^0.3.4",
|
||||
"ripple-keypairs": "^0.10.1",
|
||||
"ripple-lib-transactionparser": "0.8.0",
|
||||
"ws": "^3.3.1"
|
||||
|
||||
40
src/common/hashes/hash-prefix.ts
Normal file
40
src/common/hashes/hash-prefix.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
|
||||
enum HashPrefix {
|
||||
// transaction plus signature to give transaction ID
|
||||
TRANSACTION_ID = 0x54584E00, // 'TXN'
|
||||
|
||||
// transaction plus metadata
|
||||
TRANSACTION_NODE = 0x534E4400, // 'TND'
|
||||
|
||||
// inner node in tree
|
||||
INNER_NODE = 0x4D494E00, // 'MIN'
|
||||
|
||||
// leaf node in tree
|
||||
LEAF_NODE = 0x4D4C4E00, // 'MLN'
|
||||
|
||||
// inner transaction to sign
|
||||
TRANSACTION_SIGN = 0x53545800, // 'STX'
|
||||
|
||||
// inner transaction to sign (TESTNET)
|
||||
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'
|
||||
|
||||
// inner transaction to multisign
|
||||
TRANSACTION_MULTISIGN = 0x534D5400, // 'SMT'
|
||||
|
||||
// ledger
|
||||
LEDGER = 0x4C575200 // 'LWR'
|
||||
}
|
||||
|
||||
export default HashPrefix
|
||||
155
src/common/hashes/index.ts
Normal file
155
src/common/hashes/index.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAddress} from 'ripple-address-codec'
|
||||
import sha512Half from './sha512Half'
|
||||
import HashPrefix from './hash-prefix'
|
||||
import {SHAMap, NodeType} from './shamap'
|
||||
import {encode} from 'ripple-binary-codec'
|
||||
import ledgerspaces from './ledgerspaces'
|
||||
|
||||
const padLeftZero = (string: string, length: number): string => {
|
||||
return Array(length - string.length + 1).join('0') + string
|
||||
}
|
||||
|
||||
const intToHex = (integer: number, byteLength: number): string => {
|
||||
return padLeftZero(Number(integer).toString(16), byteLength * 2)
|
||||
}
|
||||
|
||||
const bytesToHex = (bytes: number[]): string => {
|
||||
return Buffer.from(bytes).toString('hex')
|
||||
}
|
||||
|
||||
const bigintToHex = (integerString: string | number | BigNumber, byteLength: number): string => {
|
||||
const hex = (new BigNumber(integerString)).toString(16)
|
||||
return padLeftZero(hex, byteLength * 2)
|
||||
}
|
||||
|
||||
const ledgerSpaceHex = (name: string): string => {
|
||||
return intToHex(ledgerspaces[name].charCodeAt(0), 2)
|
||||
}
|
||||
|
||||
const addressToHex = (address: string): string => {
|
||||
return (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
}
|
||||
|
||||
const currencyToHex = (currency: string): string => {
|
||||
if (currency.length === 3) {
|
||||
let bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
|
||||
bytes[12] = currency.charCodeAt(0) & 0xff
|
||||
bytes[13] = currency.charCodeAt(1) & 0xff
|
||||
bytes[14] = currency.charCodeAt(2) & 0xff
|
||||
return bytesToHex(bytes)
|
||||
}
|
||||
return currency
|
||||
}
|
||||
|
||||
const addLengthPrefix = (hex: string): string => {
|
||||
const length = hex.length / 2
|
||||
if (length <= 192) {
|
||||
return bytesToHex([length]) + hex
|
||||
} else if (length <= 12480) {
|
||||
const x = length - 193
|
||||
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex
|
||||
} else if (length <= 918744) {
|
||||
const x = length - 12481
|
||||
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex
|
||||
}
|
||||
throw new Error('Variable integer overflow.')
|
||||
}
|
||||
|
||||
export const computeBinaryTransactionHash = (txBlobHex: string): string => {
|
||||
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
|
||||
return sha512Half(prefix + txBlobHex)
|
||||
}
|
||||
|
||||
export const computeTransactionHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionHash(encode(txJSON))
|
||||
}
|
||||
|
||||
export const computeBinaryTransactionSigningHash = (txBlobHex: string): string => {
|
||||
const prefix = HashPrefix.TRANSACTION_SIGN.toString(16).toUpperCase()
|
||||
return sha512Half(prefix + txBlobHex)
|
||||
}
|
||||
|
||||
export const computeTransactionSigningHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionSigningHash(encode(txJSON))
|
||||
}
|
||||
|
||||
export const computeAccountHash = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
|
||||
}
|
||||
|
||||
export const computeSignerListHash = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('signerList') +
|
||||
addressToHex(address) +
|
||||
'00000000' /* uint32(0) signer list index */)
|
||||
}
|
||||
|
||||
export const computeOrderHash = (address: string, sequence: number): string => {
|
||||
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
|
||||
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
|
||||
}
|
||||
|
||||
export const computeTrustlineHash = (address1: string, address2: string, currency: string): string => {
|
||||
const address1Hex = addressToHex(address1)
|
||||
const address2Hex = addressToHex(address2)
|
||||
|
||||
const swap = (new BigNumber(address1Hex, 16)).greaterThan(
|
||||
new BigNumber(address2Hex, 16))
|
||||
const lowAddressHex = swap ? address2Hex : address1Hex
|
||||
const highAddressHex = swap ? address1Hex : address2Hex
|
||||
|
||||
const prefix = ledgerSpaceHex('rippleState')
|
||||
return sha512Half(prefix + lowAddressHex + highAddressHex +
|
||||
currencyToHex(currency))
|
||||
}
|
||||
|
||||
export const computeTransactionTreeHash = (transactions: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
transactions.forEach((txJSON) => {
|
||||
const txBlobHex = encode(txJSON)
|
||||
const metaHex = encode(txJSON.metaData)
|
||||
const txHash = computeBinaryTransactionHash(txBlobHex)
|
||||
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
|
||||
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
|
||||
})
|
||||
|
||||
return shamap.hash
|
||||
}
|
||||
|
||||
export const 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::updateHash()
|
||||
export const 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 const computeEscrowHash = (address, sequence): string => {
|
||||
return sha512Half(ledgerSpaceHex('escrow') + addressToHex(address) +
|
||||
intToHex(sequence, 4))
|
||||
}
|
||||
|
||||
export const computePaymentChannelHash = (address, dstAddress, sequence): string => {
|
||||
return sha512Half(ledgerSpaceHex('paychan') + addressToHex(address) +
|
||||
addressToHex(dstAddress) + intToHex(sequence, 4))
|
||||
}
|
||||
25
src/common/hashes/ledgerspaces.ts
Normal file
25
src/common/hashes/ledgerspaces.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
/**
|
||||
* Ripple ledger namespace prefixes.
|
||||
*
|
||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||
* names are partitioned into namespaces.
|
||||
*
|
||||
* Each namespace is just a single character prefix.
|
||||
*/
|
||||
export default {
|
||||
account : 'a',
|
||||
dirNode : 'd',
|
||||
generatorMap : 'g',
|
||||
rippleState : 'r',
|
||||
offer : 'o', // Entry for an offer.
|
||||
ownerDir : 'O', // Directory of things owned by an account.
|
||||
bookDir : 'B', // Directory of order books.
|
||||
contract : 'c',
|
||||
skipList : 's',
|
||||
amendment : 'f',
|
||||
feeSettings : 'e',
|
||||
signerList : 'S',
|
||||
escrow : 'u',
|
||||
paychan : 'x'
|
||||
}
|
||||
7
src/common/hashes/sha512Half.ts
Normal file
7
src/common/hashes/sha512Half.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {createHash} from 'crypto'
|
||||
|
||||
const sha512Half = (hex: string): string => {
|
||||
return createHash('sha512').update(Buffer.from(hex, 'hex')).digest('hex').toUpperCase().slice(0, 64)
|
||||
}
|
||||
|
||||
export default sha512Half
|
||||
173
src/common/hashes/shamap.ts
Normal file
173
src/common/hashes/shamap.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import hashPrefix from './hash-prefix'
|
||||
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.
|
||||
* @constructor
|
||||
*/
|
||||
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 {number} depth i.e. how many parent inner nodes
|
||||
* @constructor
|
||||
*/
|
||||
public constructor(depth: number = 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
|
||||
* @return {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 {number} slot a number 0-15
|
||||
* @param {Node} node to place
|
||||
* @return {void}
|
||||
*/
|
||||
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 {number} slot a number 0-15
|
||||
* @return {Node}
|
||||
*/
|
||||
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 {string} tag equates to a ledger entry `index`
|
||||
* @param {string} data hex of account state, transaction etc
|
||||
* @param {number} type one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc
|
||||
* @constructor
|
||||
*/
|
||||
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.
|
||||
* @constructor
|
||||
*/
|
||||
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,6 +1,6 @@
|
||||
import * as _ from 'lodash'
|
||||
import binary = require('ripple-binary-codec')
|
||||
const {computeTransactionHash} = require('ripple-hashes')
|
||||
import {computeTransactionHash} from '../common/hashes'
|
||||
import * as utils from './utils'
|
||||
import parseTransaction from './parse/transaction'
|
||||
import getTransaction from './transaction'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash'
|
||||
import hashes = require('ripple-hashes')
|
||||
import {computeLedgerHash, computeTransactionTreeHash, computeStateTreeHash} from '../common/hashes'
|
||||
import * as common from '../common'
|
||||
|
||||
function convertLedgerHeader(header): any {
|
||||
@@ -22,11 +22,11 @@ function convertLedgerHeader(header): any {
|
||||
|
||||
function hashLedgerHeader(ledgerHeader) {
|
||||
const header = convertLedgerHeader(ledgerHeader)
|
||||
return hashes.computeLedgerHash(header)
|
||||
return computeLedgerHash(header)
|
||||
}
|
||||
|
||||
function computeTransactionHash(ledger, version,
|
||||
options: ComputeLedgerHashOptions) {
|
||||
function computeTransactionHash(ledger,
|
||||
options: ComputeLedgerHeaderHashOptions) {
|
||||
let transactions: any[]
|
||||
if (ledger.rawTransactions) {
|
||||
transactions = JSON.parse(ledger.rawTransactions)
|
||||
@@ -56,7 +56,7 @@ function computeTransactionHash(ledger, version,
|
||||
tx.meta ? {metaData: tx.meta} : {})
|
||||
return renameMeta
|
||||
})
|
||||
const transactionHash = hashes.computeTransactionTreeHash(txs, version)
|
||||
const transactionHash = computeTransactionTreeHash(txs)
|
||||
if (ledger.transactionHash !== undefined
|
||||
&& ledger.transactionHash !== transactionHash) {
|
||||
throw new common.errors.ValidationError('transactionHash in header'
|
||||
@@ -68,8 +68,8 @@ function computeTransactionHash(ledger, version,
|
||||
return transactionHash
|
||||
}
|
||||
|
||||
function computeStateHash(ledger, version,
|
||||
options: ComputeLedgerHashOptions) {
|
||||
function computeStateHash(ledger,
|
||||
options: ComputeLedgerHeaderHashOptions) {
|
||||
if (ledger.rawState === undefined) {
|
||||
if (options.computeTreeHashes) {
|
||||
throw new common.errors.ValidationError('rawState'
|
||||
@@ -78,7 +78,7 @@ function computeStateHash(ledger, version,
|
||||
return ledger.stateHash
|
||||
}
|
||||
const state = JSON.parse(ledger.rawState)
|
||||
const stateHash = hashes.computeStateTreeHash(state, version)
|
||||
const stateHash = computeStateTreeHash(state)
|
||||
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
|
||||
throw new common.errors.ValidationError('stateHash in header'
|
||||
+ ' does not match computed hash of state')
|
||||
@@ -86,20 +86,17 @@ function computeStateHash(ledger, version,
|
||||
return stateHash
|
||||
}
|
||||
|
||||
const sLCF_SHAMapV2 = 0x02
|
||||
|
||||
export type ComputeLedgerHashOptions = {
|
||||
export type ComputeLedgerHeaderHashOptions = {
|
||||
computeTreeHashes?: boolean
|
||||
}
|
||||
|
||||
function computeLedgerHash(ledger: any,
|
||||
options: ComputeLedgerHashOptions = {}): string {
|
||||
const version = ((ledger.closeFlags & sLCF_SHAMapV2) === 0) ? 1 : 2
|
||||
function computeLedgerHeaderHash(ledger: any,
|
||||
options: ComputeLedgerHeaderHashOptions = {}): string {
|
||||
const subhashes = {
|
||||
transactionHash: computeTransactionHash(ledger, version, options),
|
||||
stateHash: computeStateHash(ledger, version, options)
|
||||
transactionHash: computeTransactionHash(ledger, options),
|
||||
stateHash: computeStateHash(ledger, options)
|
||||
}
|
||||
return hashLedgerHeader(_.assign({}, ledger, subhashes))
|
||||
}
|
||||
|
||||
export default computeLedgerHash
|
||||
export default computeLedgerHeaderHash
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as utils from './utils'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAddress} from 'ripple-address-codec'
|
||||
import {validate} from '../common'
|
||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
|
||||
function addressToBigNumber(address) {
|
||||
const hex = (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as isEqual from '../common/js/lodash.isequal'
|
||||
import * as utils from './utils'
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import binaryCodec = require('ripple-binary-codec')
|
||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
import {SignOptions, KeyPair} from './types'
|
||||
import {BigNumber} from 'bignumber.js'
|
||||
import {xrpToDrops} from '../common'
|
||||
|
||||
1
test/fixtures/rippled/ledger-full-40000.json
vendored
Normal file
1
test/fixtures/rippled/ledger-full-40000.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/rippled/ledger-full-7501326.json
vendored
Normal file
1
test/fixtures/rippled/ledger-full-7501326.json
vendored
Normal file
File diff suppressed because one or more lines are too long
127
test/hashes-test.js
Normal file
127
test/hashes-test.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/* eslint-disable max-len, valid-jsdoc */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var hashes = require('../src/common/hashes');
|
||||
|
||||
/**
|
||||
* @param ledgerIndex {Number}
|
||||
* Expects a corresponding ledger dump in $repo/test/fixtures/rippled folder
|
||||
*/
|
||||
function createLedgerTest(ledgerIndex) {
|
||||
describe(String(ledgerIndex), function() {
|
||||
var path = __dirname + '/fixtures/rippled/ledger-full-' + ledgerIndex + '.json';
|
||||
|
||||
var ledgerRaw = fs.readFileSync(path);
|
||||
var ledgerJSON = JSON.parse(ledgerRaw);
|
||||
|
||||
var hasAccounts = Array.isArray(ledgerJSON.accountState)
|
||||
&& ledgerJSON.accountState.length > 0;
|
||||
|
||||
if (hasAccounts) {
|
||||
it('has account_hash of ' + ledgerJSON.account_hash, function() {
|
||||
assert.equal(ledgerJSON.account_hash,
|
||||
hashes.computeStateTreeHash(ledgerJSON.accountState, 1));
|
||||
});
|
||||
}
|
||||
it('has transaction_hash of ' + ledgerJSON.transaction_hash, function() {
|
||||
assert.equal(ledgerJSON.transaction_hash,
|
||||
hashes.computeTransactionTreeHash(ledgerJSON.transactions, 1));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('Ledger', function() {
|
||||
// This is the first recorded ledger with a non empty transaction set
|
||||
createLedgerTest(38129);
|
||||
// Because, why not.
|
||||
createLedgerTest(40000);
|
||||
// 1311 AffectedNodes, no accounts
|
||||
createLedgerTest(7501326);
|
||||
|
||||
describe('calcAccountRootEntryHash', function() {
|
||||
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
|
||||
var actualEntryHash = hashes.computeAccountHash(account);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcRippleStateEntryHash', function() {
|
||||
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
|
||||
var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
|
||||
var currency = 'USD';
|
||||
|
||||
var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
|
||||
var actualEntryHash1 = hashes.computeTrustlineHash(
|
||||
account1, account2, currency);
|
||||
var actualEntryHash2 = hashes.computeTrustlineHash(
|
||||
account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1, expectedEntryHash);
|
||||
assert.equal(actualEntryHash2, expectedEntryHash);
|
||||
});
|
||||
|
||||
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
|
||||
var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
|
||||
var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
|
||||
var currency = 'UAM';
|
||||
|
||||
var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
|
||||
var actualEntryHash1 = hashes.computeTrustlineHash(
|
||||
account1, account2, currency);
|
||||
var actualEntryHash2 = hashes.computeTrustlineHash(
|
||||
account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1, expectedEntryHash);
|
||||
assert.equal(actualEntryHash2, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcOfferEntryHash', function() {
|
||||
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
|
||||
var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
|
||||
var sequence = 137;
|
||||
var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
|
||||
var actualEntryHash = hashes.computeOrderHash(account, sequence);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeSignerListHash', function() {
|
||||
it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var expectedEntryHash = '778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF';
|
||||
var actualEntryHash = hashes.computeSignerListHash(account);
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcEscrowEntryHash', function() {
|
||||
it('will calculate the Escrow entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x, sequence 84', function() {
|
||||
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
|
||||
var sequence = 84;
|
||||
var expectedEntryHash = '61E8E8ED53FA2CEBE192B23897071E9A75217BF5A410E9CB5B45AAB7AECA567A';
|
||||
var actualEntryHash = hashes.computeEscrowHash(account, sequence);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcPaymentChannelEntryHash', function() {
|
||||
it('will calculate the PaymentChannel entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x and rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso, sequence 82', function() {
|
||||
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
|
||||
var dstAccount = 'rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso'
|
||||
var sequence = 82;
|
||||
var expectedEntryHash = 'E35708503B3C3143FB522D749AAFCC296E8060F0FB371A9A56FAE0B1ED127366';
|
||||
var actualEntryHash = hashes.computePaymentChannelHash(account, dstAccount, sequence);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
71
test/shamap-test.js
Normal file
71
test/shamap-test.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/* eslint-disable max-len, valid-jsdoc */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var SHAMap = require('../src/common/hashes/shamap').SHAMap;
|
||||
var TYPE_TRANSACTION_NO_METADATA = require('../src/common/hashes/shamap').NodeType.TRANSACTION_NO_METADATA
|
||||
|
||||
var HEX_ZERO = '00000000000000000000000000000000' +
|
||||
'00000000000000000000000000000000';
|
||||
|
||||
/**
|
||||
* Generates data to hash for testing
|
||||
* @param {number} v int value
|
||||
* @returns {string} 64 length hex string
|
||||
*/
|
||||
function intToVuc(v) {
|
||||
var ret = '';
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
ret += '0';
|
||||
ret += v.toString(16).toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shamap {Object}
|
||||
* @param keys {Array}
|
||||
* @param hashes {Array}
|
||||
*/
|
||||
function fillShamapTest(shamap, keys, hashes) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var data = intToVuc(i);
|
||||
shamap.addItem(keys[i].toUpperCase(), data, TYPE_TRANSACTION_NO_METADATA);
|
||||
assert.equal(shamap.hash, hashes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
describe('SHAMap', function() {
|
||||
|
||||
describe('#addItem', function() {
|
||||
it('will add new nodes to v1', function() {
|
||||
var keys = [
|
||||
'b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8'
|
||||
];
|
||||
|
||||
var hashesv1 = [
|
||||
'B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C64B6281F7F',
|
||||
'FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A8831845467FB2ECE266',
|
||||
'4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51AE756795B75',
|
||||
'7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC6C74F93E07',
|
||||
'395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596462B0E3A3E',
|
||||
'D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D98A84D9DDE4',
|
||||
'76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF94361143615',
|
||||
'DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA02BBE7230E5'
|
||||
];
|
||||
|
||||
|
||||
var shamapv1 = new SHAMap();
|
||||
assert.equal(shamapv1.hash, HEX_ZERO);
|
||||
fillShamapTest(shamapv1, keys, hashesv1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user