From 8f37438a0827e394cb3eb28c10af5bd1bb380428 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Tue, 16 Jun 2015 18:47:43 -0700 Subject: [PATCH] Convert getAccountTransactions --- .flowconfig | 4 +- src/api/api.js | 1 + src/api/ledger/parse/amount.js | 9 +- src/api/ledger/parse/cancellation.js | 3 +- src/api/ledger/parse/fields.js | 3 +- src/api/ledger/parse/notification.js | 1 + src/api/ledger/parse/order.js | 3 +- src/api/ledger/parse/pathfind.js | 4 +- src/api/ledger/parse/payment.js | 3 +- src/api/ledger/parse/settings.js | 3 +- src/api/ledger/parse/transaction.js | 3 +- src/api/ledger/parse/trustline.js | 9 +- src/api/ledger/parse/utils.js | 8 +- src/api/ledger/transactions.js | 272 +++++------------- src/api/ledger/utils.js | 8 +- test/api-test.js | 8 + .../account-transactions-response.json | 180 ++++++++++++ test/fixtures/acct-tx-response.js | 209 ++++++++++++++ test/fixtures/mock.js | 3 + test/fixtures/transaction-response.json | 1 + test/mock-rippled.js | 8 + 21 files changed, 514 insertions(+), 229 deletions(-) create mode 100644 test/fixtures/account-transactions-response.json create mode 100644 test/fixtures/acct-tx-response.js diff --git a/.flowconfig b/.flowconfig index 72432861..b811c490 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,9 +1,11 @@ [ignore] -.*/src/.* +.*/src/api/.* +.*/src/core/.* .*/dist/.* .*/test/fixtures/.* [include] +./node_modules/ [libs] diff --git a/src/api/api.js b/src/api/api.js index b062745d..25b15474 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -40,6 +40,7 @@ RippleAPI.prototype = { getOrderBook: orders.getOrderBook, getSettings: settings.getSettings, getTransaction: transactions.getTransaction, + getAccountTransactions: transactions.getAccountTransactions, getNotification: notifications.getNotification, getNotifications: notifications.getNotifications, diff --git a/src/api/ledger/parse/amount.js b/src/api/ledger/parse/amount.js index 089c27e5..6e34f6bf 100644 --- a/src/api/ledger/parse/amount.js +++ b/src/api/ledger/parse/amount.js @@ -1,7 +1,12 @@ +/* @flow */ 'use strict'; const utils = require('./utils'); -function parseAmount(amount) { +/*:: type Amount = string | {currency: string, issuer: string, value: string} */ +/*:: type XRPAmount = {currency: string, value: string} */ +/*:: type IOUAmount = {currency: string, value: string, counterparty: string} */ +/*:: type Output = XRPAmount | IOUAmount */ +function parseAmount(amount: Amount): Output { if (typeof amount === 'string') { return { currency: 'XRP', @@ -11,7 +16,7 @@ function parseAmount(amount) { return { currency: amount.currency, value: amount.value, - counterparty: amount.issuer || amount.counterparty + counterparty: amount.issuer }; } diff --git a/src/api/ledger/parse/cancellation.js b/src/api/ledger/parse/cancellation.js index 1ce6c984..34a783ff 100644 --- a/src/api/ledger/parse/cancellation.js +++ b/src/api/ledger/parse/cancellation.js @@ -1,7 +1,8 @@ +/* @flow */ 'use strict'; const assert = require('assert'); -function parseOrderCancellation(tx) { +function parseOrderCancellation(tx: Object): Object { assert(tx.TransactionType === 'OfferCancel'); return { orderSequence: tx.OfferSequence diff --git a/src/api/ledger/parse/fields.js b/src/api/ledger/parse/fields.js index 685bef6b..132fcf82 100644 --- a/src/api/ledger/parse/fields.js +++ b/src/api/ledger/parse/fields.js @@ -1,3 +1,4 @@ +/* @flow */ 'use strict'; const AccountFields = require('./utils').constants.AccountFields; @@ -8,7 +9,7 @@ function parseField(info, value) { return value; } -function parseFields(data) { +function parseFields(data: Object): Object { const settings = {}; for (const fieldName in AccountFields) { const fieldValue = data[fieldName]; diff --git a/src/api/ledger/parse/notification.js b/src/api/ledger/parse/notification.js index fec685b8..35f26438 100644 --- a/src/api/ledger/parse/notification.js +++ b/src/api/ledger/parse/notification.js @@ -1,3 +1,4 @@ +/* @flow */ /* eslint-disable valid-jsdoc */ 'use strict'; const ripple = require('../utils').common.core; diff --git a/src/api/ledger/parse/order.js b/src/api/ledger/parse/order.js index fcc41935..fb560266 100644 --- a/src/api/ledger/parse/order.js +++ b/src/api/ledger/parse/order.js @@ -1,10 +1,11 @@ +/* @flow */ 'use strict'; const assert = require('assert'); const utils = require('./utils'); const parseAmount = require('./amount'); const flags = utils.core.Transaction.flags.OfferCreate; -function parseOrder(tx) { +function parseOrder(tx: Object): Object { assert(tx.TransactionType === 'OfferCreate'); const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'; diff --git a/src/api/ledger/parse/pathfind.js b/src/api/ledger/parse/pathfind.js index 7ccc37bf..b99d87a2 100644 --- a/src/api/ledger/parse/pathfind.js +++ b/src/api/ledger/parse/pathfind.js @@ -1,7 +1,9 @@ +/* @flow */ 'use strict'; const parseAmount = require('./amount'); -function parsePathfind(sourceAddress, destinationAmount, pathfindResult) { +function parsePathfind(sourceAddress: string, + destinationAmount: Object, pathfindResult: Object): Object { return pathfindResult.alternatives.map(function(alternative) { return { source: { diff --git a/src/api/ledger/parse/payment.js b/src/api/ledger/parse/payment.js index 11213b7c..3c1cb51e 100644 --- a/src/api/ledger/parse/payment.js +++ b/src/api/ledger/parse/payment.js @@ -1,3 +1,4 @@ +/* @flow */ 'use strict'; const assert = require('assert'); const utils = require('./utils'); @@ -19,7 +20,7 @@ function parsePaymentMemos(tx) { return tx.Memos.map((m) => m.Memo); } -function parsePayment(tx) { +function parsePayment(tx: Object): Object { assert(tx.TransactionType === 'Payment'); const source = { diff --git a/src/api/ledger/parse/settings.js b/src/api/ledger/parse/settings.js index 1d27e435..51827c31 100644 --- a/src/api/ledger/parse/settings.js +++ b/src/api/ledger/parse/settings.js @@ -1,3 +1,4 @@ +/* @flow */ 'use strict'; const _ = require('lodash'); const assert = require('assert'); @@ -8,7 +9,7 @@ function getName(flagNumber) { return _.findKey(AccountSetFlags, (v) => v === flagNumber); } -function parseSettings(tx) { +function parseSettings(tx: Object) { const txType = tx.TransactionType; assert(txType === 'AccountSet' || txType === 'SetRegularKey'); const settings = {}; diff --git a/src/api/ledger/parse/transaction.js b/src/api/ledger/parse/transaction.js index f086942f..19599b33 100644 --- a/src/api/ledger/parse/transaction.js +++ b/src/api/ledger/parse/transaction.js @@ -1,3 +1,4 @@ +/* @flow */ 'use strict'; const assert = require('assert'); const utils = require('./utils'); @@ -19,7 +20,7 @@ function parseTransactionType(type) { return mapping[type] || null; } -function parseTransaction(tx) { +function parseTransaction(tx: Object): ?Object { const type = parseTransactionType(tx.TransactionType); const mapping = { 'payment': parsePayment, diff --git a/src/api/ledger/parse/trustline.js b/src/api/ledger/parse/trustline.js index 140bada9..70ca8c8b 100644 --- a/src/api/ledger/parse/trustline.js +++ b/src/api/ledger/parse/trustline.js @@ -1,9 +1,10 @@ +/* @flow */ 'use strict'; const assert = require('assert'); const utils = require('./utils'); const flags = utils.core.Transaction.flags.TrustSet; -function parseTrustline(tx) { +function parseTrustline(tx: Object): Object { assert(tx.TransactionType === 'TrustSet'); return { @@ -12,9 +13,9 @@ function parseTrustline(tx) { counterparty: tx.LimitAmount.issuer, qualityIn: tx.QualityIn, qualityOut: tx.QualityOut, - allowRippling: tx.Flags & flags.NoRipple === 0, - frozen: tx.Flags & flags.SetFreeze !== 0, - authorized: tx.Flags & flags.SetAuth !== 0 + allowRippling: (tx.Flags & flags.NoRipple) === 0, + frozen: (tx.Flags & flags.SetFreeze) !== 0, + authorized: (tx.Flags & flags.SetAuth) !== 0 }; } diff --git a/src/api/ledger/parse/utils.js b/src/api/ledger/parse/utils.js index 87dd3ee4..33241b7c 100644 --- a/src/api/ledger/parse/utils.js +++ b/src/api/ledger/parse/utils.js @@ -1,18 +1,19 @@ +/* @flow */ 'use strict'; const _ = require('lodash'); const transactionParser = require('ripple-lib-transactionparser'); const toTimestamp = require('../../../core/utils').toTimestamp; const utils = require('../utils'); -function parseTimestamp(tx) { +function parseTimestamp(tx: {date: string}): string | void { return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined; } -function removeUndefined(obj) { +function removeUndefined(obj: ?Object): ?Object { return obj ? _.omit(obj, _.isUndefined) : obj; } -function parseOutcome(tx) { +function parseOutcome(tx: Object): ?Object { if (!tx.validated) { return undefined; } @@ -27,6 +28,7 @@ function parseOutcome(tx) { balanceChanges: balanceChanges, orderbookChanges: orderbookChanges, ledgerVersion: tx.ledger_index, + indexInLedger: tx.meta.TransactionIndex, sequence: tx.Sequence }; } diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index 995d34ad..233f6a59 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -7,7 +7,7 @@ const parseTransaction = require('./parse/transaction'); const validate = utils.common.validate; const errors = utils.common.errors; -const DEFAULT_RESULTS_PER_PAGE = 10; +const DEFAULT_LIMIT = 100; const MIN_LEDGER_VERSION = 32570; // earlier versions have been completely lost function hasCompleteLedgerRange(remote, options) { @@ -74,228 +74,86 @@ function getTransaction(identifier, options, callback) { ], callbackWrapper); } -/** - * Wrapper around the standard ripple-lib requestAccountTx function - * - * @param {Remote} remote - * @param {RippleAddress} options.account - * @param {Number} [-1] options.ledger_index_min - * @param {Number} [-1] options.ledger_index_max - * @param {Boolean} [false] options.earliestFirst - * @param {Boolean} [false] options.binary - * @param {opaque value} options.marker - * @param {Function} callback - * - * @callback - * @param {Error} error - * @param {Array of transactions in JSON format} response.transactions - * @param {opaque value} response.marker - */ -function getAccountTx(api, options, callback) { +function parseAccountTxTransaction(tx) { + // rippled uses a different response format for 'account_tx' than 'tx' + tx.tx.meta = tx.meta; + tx.tx.validated = tx.validated; + return parseTransaction(tx.tx); +} + +function getAccountTx(remote, address, limit, marker, options, callback) { const params = { - account: options.account, - ledger_index_min: options.ledger_index_min || options.ledger_index || -1, - ledger_index_max: options.ledger_index_max || options.ledger_index || -1, - limit: options.limit || DEFAULT_RESULTS_PER_PAGE, + account: address, + ledger_index_min: options.ledgerVersion || options.minLedgerVersion || -1, + ledger_index_max: options.ledgerVersion || options.maxLedgerVersion || -1, forward: options.earliestFirst, - marker: options.marker + binary: options.binary, + limit: Math.min(limit || DEFAULT_LIMIT, 10), + marker: marker }; - if (options.binary) { - params.binary = true; + + remote.requestAccountTx(params, (error, data) => { + return error ? callback(error) : callback(null, { + transactions: data.transactions.filter((tx) => tx.validated) + .map(parseAccountTxTransaction), + marker: data.marker + }); + }); +} + +function transactionFilter(address, filters, tx) { + if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') { + return false; } - api.remote.requestAccountTx(params, function(error, account_tx_results) { - if (error) { - return callback(error); - } - const transactions = []; - account_tx_results.transactions.forEach(function(tx_entry) { - if (!tx_entry.validated) { - return; - } - const tx = tx_entry.tx; - tx.meta = tx_entry.meta; - tx.validated = tx_entry.validated; - transactions.push(tx); - }); - callback(null, { - transactions: transactions, - marker: account_tx_results.marker - }); - }); + if (filters.types && !_.includes(filters.types, tx.type)) { + return false; + } + if (filters.outgoing && tx.address !== address) { + return false; + } + if (filters.incoming && tx.address === address) { + return false; + } + return true; } -/** - * Filter transactions based on the given set of options. - * - * @param {Array of transactions in JSON format} transactions - * @param {Boolean} [false] options.exclude_failed - * @param {Array of Strings} options.types Possible values are "payment", - * "offercreate", "offercancel", "trustset", "accountset" - * @param {RippleAddress} options.source_account - * @param {RippleAddress} options.destination_account - * @param {String} options.direction Possible values are "incoming", "outgoing" - * - * @returns {Array of transactions in JSON format} filtered_transactions - */ -function transactionFilter(transactions, options) { - const filtered_transactions = transactions.filter(function(transaction) { - if (options.exclude_failed) { - if (transaction.state === 'failed' || (transaction.meta - && transaction.meta.TransactionResult !== 'tesSUCCESS')) { - return false; - } - } - if (options.types && options.types.length > 0) { - if (options.types.indexOf( - transaction.TransactionType.toLowerCase()) === -1) { - return false; - } - } - if (options.source_account) { - if (transaction.Account !== options.source_account) { - return false; - } - } - if (options.destination_account) { - if (transaction.Destination !== options.destination_account) { - return false; - } - } - if (options.direction) { - if (options.direction === 'outgoing' - && transaction.Account !== options.account) { - return false; - } - if (options.direction === 'incoming' && transaction.Destination - && transaction.Destination !== options.account) { - return false; - } - } - return true; - }); - - return filtered_transactions; -} - -function getTransactionsHelper(api, options, callback) { - getAccountTx(api, options, function(error, results) { +function getAccountTransactionsRecursive( + remote, address, limit, marker, options, callback) { + getAccountTx(remote, address, limit, marker, options, (error, data) => { if (error) { callback(error); + return; + } + const filter = _.partial(transactionFilter, address, options); + const unfilteredTransactions = data.transactions; + const filteredTransactions = unfilteredTransactions.filter(filter); + const isExhausted = unfilteredTransactions.length === 0; + if (!isExhausted && filteredTransactions.length < limit) { + const remaining = limit - filteredTransactions.length; + getAccountTransactionsRecursive( + remote, address, remaining, data.marker, options, (_err, txs) => { + return error ? callback(_err) : + callback(null, filteredTransactions.concat(txs)); + }); } else { - // Set marker so that when this function is called again - // recursively it starts from the last place it left off - options.marker = results.marker; - callback(null, results.transactions); + callback(null, filteredTransactions.slice(0, limit)); } }); } -/** - * Recursively get transactions for the specified account from - * the Remote. If options.min is set, this will - * recurse until it has retrieved that number of transactions or - * it has reached the end of the account's transaction history. - * - * @param {Remote} remote - * @param {RippleAddress} options.account - * @param {Number} [-1] options.ledger_index_min - * @param {Number} [-1] options.ledger_index_max - * @param {Boolean} [false] options.earliestFirst - * @param {Boolean} [false] options.binary - * @param {Boolean} [false] options.exclude_failed - * @param {Number} [DEFAULT_RESULTS_PER_PAGE] options.min - * @param {Number} [DEFAULT_RESULTS_PER_PAGE] options.max - * @param {Array of Strings} options.types Possible values are "payment", - * "offercreate", "offercancel", "trustset", "accountset" - * @param {opaque value} options.marker - * @param {Array of Transactions} options.previous_transactions - * Included automatically when this function is called recursively - * @param {Express.js Response} res - * @param {Function} callback - * - * @callback - * @param {Error} error - * @param {Array of transactions in JSON format} transactions - */ -function getAccountTransactions(api, options, callback) { - try { - validate.address(options.account); - } catch(err) { - return callback(err); - } +function getAccountTransactions(address, options, callback) { + validate.address(address); - if (!options.min) { - options.min = module.exports.DEFAULT_RESULTS_PER_PAGE; - } - if (!options.max) { - options.max = Math.max(options.min, - module.exports.DEFAULT_RESULTS_PER_PAGE); - } - if (!options.limit) { - options.limit = module.exports.DEFAULT_LIMIT; - } - - function queryTransactions(async_callback) { - getTransactionsHelper(api, options, async_callback); - } - - function filterTransactions(transactions, async_callback) { - async_callback(null, transactionFilter(transactions, options)); - } - - function sortTransactions(transactions, async_callback) { - const compare = options.earliestFirst ? utils.compareTransactions : - _.rearg(utils.compareTransactions, 1, 0); - transactions.sort(compare); - async_callback(null, transactions); - } - - function mergeAndTruncateResults(txns, async_callback) { - let transactions = txns; - if (options.previous_transactions - && options.previous_transactions.length > 0) { - transactions = options.previous_transactions.concat(transactions); - } - if (options.offset && options.offset > 0) { - const offset_remaining = options.offset - transactions.length; - transactions = transactions.slice(options.offset); - options.offset = offset_remaining; - } - if (transactions.length > options.max) { - transactions = transactions.slice(0, options.max); - } - async_callback(null, transactions); - } - - function asyncWaterfallCallback(error, transactions) { - if (error) { - return callback(error); - } - if (!options.min || transactions.length >= options.min || !options.marker) { - callback(null, transactions); - } else { - options.previous_transactions = transactions; - setImmediate(function() { - getAccountTransactions(api, options, callback); - }); - } - } - - const steps = [ - queryTransactions, - filterTransactions, - sortTransactions, - mergeAndTruncateResults - ]; - - async.waterfall(steps, asyncWaterfallCallback); + const limit = options.limit || DEFAULT_LIMIT; + const compare = options.earliestFirst ? utils.compareTransactions : + _.rearg(utils.compareTransactions, 1, 0); + getAccountTransactionsRecursive( + this.remote, address, limit, null, options, (error, transactions) => { + return error ? callback(error) : callback(null, transactions.sort(compare)); + }); } module.exports = { - DEFAULT_LIMIT: 200, - DEFAULT_RESULTS_PER_PAGE: DEFAULT_RESULTS_PER_PAGE, - NUM_TRANSACTION_TYPES: 5, - DEFAULT_LEDGER_BUFFER: 3, getTransaction: utils.wrapCatch(getTransaction), - getAccountTransactions: getAccountTransactions + getAccountTransactions: utils.wrapCatch(getAccountTransactions) }; diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index 47fc83d5..4704be0c 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -58,12 +58,10 @@ function signum(num) { * @returns {Number} [-1, 0, 1] */ function compareTransactions(first, second) { - if (first.ledger_index === second.ledger_index) { - return signum( - Number(first.meta.TransactionIndex) - - Number(second.meta.TransactionIndex)); + if (first.ledgerVersion === second.ledgerVersion) { + return signum(Number(first.indexInLedger) - Number(second.indexInLedger)); } - return Number(first.ledger_index) < Number(second.ledger_index) ? -1 : 1; + return Number(first.ledgerVersion) < Number(second.ledgerVersion) ? -1 : 1; } function attachDate(api, baseTransactions, callback) { diff --git a/test/api-test.js b/test/api-test.js index 5e2717e1..9b7424b7 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -22,6 +22,8 @@ const MockPRNG = require('./mock-prng'); const sjcl = require('../src').sjcl; const submitResponse = require('./fixtures/submit-response'); const transactionResponse = require('./fixtures/transaction-response'); +const accountTransactionsResponse = + require('./fixtures/account-transactions-response'); function checkResult(expected, done, error, response) { if (error) { @@ -91,4 +93,10 @@ describe('RippleAPI', function() { this.api.getTransaction(hashes.VALID_TRANSACTION_HASH, {}, _.partial(checkResult, transactionResponse, done)); }); + + it('getAccountTransactions', function(done) { + const options = {types: ['payment', 'order'], outgoing: true, limit: 2}; + this.api.getAccountTransactions(address, options, + _.partial(checkResult, accountTransactionsResponse, done)); + }); }); diff --git a/test/fixtures/account-transactions-response.json b/test/fixtures/account-transactions-response.json new file mode 100644 index 00000000..184cad0e --- /dev/null +++ b/test/fixtures/account-transactions-response.json @@ -0,0 +1,180 @@ +[ + { + "type": "payment", + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "specification": { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "XRP", + "value": "1.112209" + } + }, + "destination": { + "address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "amount": { + "currency": "USD", + "value": "0.001", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM" + } + }, + "paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]", + "allowPartialPayment": false, + "noDirectRipple": false + }, + "outcome": { + "result": "tesSUCCESS", + "fee": "0.00001", + "balanceChanges": { + "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [ + { + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "currency": "USD", + "value": "-0.001" + }, + { + "counterparty": "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr", + "currency": "USD", + "value": "0.001002" + } + ], + "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM": [ + { + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currency": "USD", + "value": "0.001" + } + ], + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59": [ + { + "counterparty": "", + "currency": "XRP", + "value": "-1.101208" + } + ], + "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr": [ + { + "counterparty": "", + "currency": "XRP", + "value": "1.101198" + }, + { + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currency": "USD", + "value": "-0.001002" + } + ] + }, + "orderbookChanges": { + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59": [ + { + "taker_pays": { + "currency": "XRP", + "counterparty": "", + "value": "-1.101198" + }, + "taker_gets": { + "currency": "USD", + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "value": "-0.001002" + }, + "sequence": 58, + "status": "open" + } + ] + }, + "ledgerVersion": 348860, + "indexInLedger": 0, + "sequence": 4 + } + }, + { + "type": "payment", + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "specification": { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "XRP", + "value": "1.112209" + } + }, + "destination": { + "address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "amount": { + "currency": "USD", + "value": "0.001", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM" + } + }, + "paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]", + "allowPartialPayment": false, + "noDirectRipple": false + }, + "outcome": { + "result": "tesSUCCESS", + "fee": "0.00001", + "balanceChanges": { + "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [ + { + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "currency": "USD", + "value": "-0.001" + }, + { + "counterparty": "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr", + "currency": "USD", + "value": "0.001002" + } + ], + "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM": [ + { + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currency": "USD", + "value": "0.001" + } + ], + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59": [ + { + "counterparty": "", + "currency": "XRP", + "value": "-1.101208" + } + ], + "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr": [ + { + "counterparty": "", + "currency": "XRP", + "value": "1.101198" + }, + { + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currency": "USD", + "value": "-0.001002" + } + ] + }, + "orderbookChanges": { + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59": [ + { + "taker_pays": { + "currency": "XRP", + "counterparty": "", + "value": "-1.101198" + }, + "taker_gets": { + "currency": "USD", + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "value": "-0.001002" + }, + "sequence": 58, + "status": "open" + } + ] + }, + "ledgerVersion": 348860, + "indexInLedger": 0, + "sequence": 4 + } + } +] diff --git a/test/fixtures/acct-tx-response.js b/test/fixtures/acct-tx-response.js new file mode 100644 index 00000000..6ea7555a --- /dev/null +++ b/test/fixtures/acct-tx-response.js @@ -0,0 +1,209 @@ +/* eslint-disable max-len */ +'use strict'; +const _ = require('lodash'); +const hashes = require('./hashes'); +const addresses = require('./addresses'); +const SerializedObject = require('../../src/core').SerializedObject; + +module.exports = function(request, options={}) { + _.defaults(options, { + memos: [], + hash: hashes.VALID_TRANSACTION_HASH, + validated: true + }); + + const tx = { + Account: addresses.ACCOUNT, + Amount: { + currency: 'USD', + issuer: addresses.ISSUER, + value: '0.001' + }, + Destination: addresses.ISSUER, + Fee: '10', + Flags: 0, + Memos: options.memos, + Paths: [ + [ + { + currency: 'USD', + issuer: addresses.OTHER_ACCOUNT, + type: 48, + type_hex: '0000000000000030' + }, + { + account: addresses.OTHER_ACCOUNT, + currency: 'USD', + issuer: addresses.OTHER_ACCOUNT, + type: 49, + type_hex: '0000000000000031' + } + ] + ], + SendMax: '1112209', + Sequence: 4, + SigningPubKey: '02BC8C02199949B15C005B997E7C8594574E9B02BA2D0628902E0532989976CF9D', + TransactionType: 'Payment', + TxnSignature: '304502204EE3E9D1B01D8959B08450FCA9E22025AF503DEF310E34A93863A85CAB3C0BC5022100B61F5B567F77026E8DEED89EED0B7CAF0E6C96C228A2A65216F0DC2D04D52083' + }; + + const meta = { + AffectedNodes: [ + { + ModifiedNode: { + FinalFields: { + Account: addresses.ACCOUNT, + BookDirectory: '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5E03E788E09BB000', + BookNode: '0000000000000000', + Flags: 0, + OwnerNode: '0000000000000000', + Sequence: 58, + TakerGets: { + currency: 'USD', + issuer: addresses.OTHER_ACCOUNT, + value: '5.648998' + }, + TakerPays: '6208248802' + }, + LedgerEntryType: 'Offer', + LedgerIndex: '3CFB3C79D4F1BDB1EE5245259372576D926D9A875713422F7169A6CC60AFA68B', + PreviousFields: { + TakerGets: { + currency: 'USD', + issuer: addresses.OTHER_ACCOUNT, + value: '5.65' + }, + TakerPays: '6209350000' + }, + PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4', + PreviousTxnLgrSeq: 348433 + } + }, + { + ModifiedNode: { + FinalFields: { + Balance: { + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', + value: '-0.001' + }, + Flags: 131072, + HighLimit: { + currency: 'USD', + issuer: addresses.ISSUER, + value: '1' + }, + HighNode: '0000000000000000', + LowLimit: { + currency: 'USD', + issuer: addresses.OTHER_ACCOUNT, + value: '0' + }, + LowNode: '0000000000000002' + }, + LedgerEntryType: 'RippleState', + LedgerIndex: '4BD1874F8F3A60EDB0C23F5BD43E07953C2B8741B226648310D113DE2B486F01', + PreviousFields: { + Balance: { + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', + value: '0' + } + }, + PreviousTxnID: '5B2006DAD0B3130F57ACF7CC5CCAC2EEBCD4B57AAA091A6FD0A24B073D08ABB8', + PreviousTxnLgrSeq: 343703 + } + }, + { + ModifiedNode: { + FinalFields: { + Account: addresses.ACCOUNT, + Balance: '9998898762', + Flags: 0, + OwnerCount: 3, + Sequence: 5 + }, + LedgerEntryType: 'AccountRoot', + LedgerIndex: '4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05', + PreviousFields: { + Balance: '9999999970', + Sequence: 4 + }, + PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', + PreviousTxnLgrSeq: 343570 + } + }, + { + ModifiedNode: { + FinalFields: { + Account: 'r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr', + Balance: '912695302618', + Flags: 0, + OwnerCount: 10, + Sequence: 59 + }, + LedgerEntryType: 'AccountRoot', + LedgerIndex: 'F3E119AAA87AF3607CF87F5523BB8278A83BCB4142833288305D767DD30C392A', + PreviousFields: { + Balance: '912694201420' + }, + PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4', + PreviousTxnLgrSeq: 348433 + } + }, + { + ModifiedNode: { + FinalFields: { + Balance: { + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', + value: '-5.5541638883365' + }, + Flags: 131072, + HighLimit: { + currency: 'USD', + issuer: 'r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr', + value: '1000' + }, + HighNode: '0000000000000000', + LowLimit: { + currency: 'USD', + issuer: addresses.OTHER_ACCOUNT, + value: '0' + }, + LowNode: '000000000000000C' + }, + LedgerEntryType: 'RippleState', + LedgerIndex: 'FA1255C2E0407F1945BCF9351257C7C5C28B0F5F09BB81C08D35A03E9F0136BC', + PreviousFields: { + Balance: { + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrrBZbvji', + value: '-5.5551658883365' + } + }, + PreviousTxnID: '8F571C346688D89AC1F737AE3B6BB5D976702B171CC7B4DE5CA3D444D5B8D6B4', + PreviousTxnLgrSeq: 348433 + } + } + ], + TransactionIndex: 0, + TransactionResult: 'tesSUCCESS' + }; + + return JSON.stringify({ + id: request.id, + status: 'success', + type: 'response', + result: { + transactions: [ + { + ledger_index: 348860, + tx_blob: SerializedObject.from_json(tx).to_hex(), + meta: SerializedObject.from_json(meta).to_hex(), + validated: options.validated + } + ] + } + }); +}; diff --git a/test/fixtures/mock.js b/test/fixtures/mock.js index 7856bd74..0fa6a038 100644 --- a/test/fixtures/mock.js +++ b/test/fixtures/mock.js @@ -2,9 +2,12 @@ 'use strict'; const _ = require('lodash'); const addresses = require('./addresses'); +const accountTransactionsResponse = require('./acct-tx-response'); const SerializedObject = require('ripple-lib').SerializedObject; const BASE_LEDGER_INDEX = 8819951; +module.exports.accountTransactionsResponse = accountTransactionsResponse; + module.exports.subscribeResponse = function(request) { return JSON.stringify({ id: request.id, diff --git a/test/fixtures/transaction-response.json b/test/fixtures/transaction-response.json index 293ed657..339e01ee 100644 --- a/test/fixtures/transaction-response.json +++ b/test/fixtures/transaction-response.json @@ -84,6 +84,7 @@ ] }, "ledgerVersion": 348860, + "indexInLedger": 0, "sequence": 4 } } diff --git a/test/mock-rippled.js b/test/mock-rippled.js index a074f558..82cc08b4 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -112,5 +112,13 @@ module.exports = function(port) { } }); + mock.on('request_account_tx', function(request, conn) { + if (request.account === addresses.ACCOUNT) { + conn.send(fixtures.accountTransactionsResponse(request)); + } else { + assert(false, 'Unrecognized account address: ' + request.account); + } + }); + return mock; };