Fix: Emit error events and return error on pathfind

This commit is contained in:
Alan Cohen
2015-08-20 11:47:33 -07:00
committed by Chris Clark
parent ba6c703163
commit 1ccbaf6776
21 changed files with 129 additions and 66 deletions

View File

@@ -77,13 +77,13 @@ ApiError.prototype = new RippleError();
ApiError.prototype.name = 'ApiError'; ApiError.prototype.name = 'ApiError';
module.exports = { module.exports = {
ValidationError: ValidationError, ValidationError,
NetworkError: NetworkError, NetworkError,
TransactionError: TransactionError, TransactionError,
RippledNetworkError: RippledNetworkError, RippledNetworkError,
NotFoundError: NotFoundError, NotFoundError,
MissingLedgerHistoryError: MissingLedgerHistoryError, MissingLedgerHistoryError,
TimeOutError: TimeOutError, TimeOutError,
ApiError: ApiError, ApiError,
RippleError: RippleError RippleError
}; };

View File

@@ -12,6 +12,7 @@ module.exports = {
generateAddress: utils.generateAddress, generateAddress: utils.generateAddress,
composeAsync: utils.composeAsync, composeAsync: utils.composeAsync,
wrapCatch: utils.wrapCatch, wrapCatch: utils.wrapCatch,
convertErrors: utils.convertErrors,
convertExceptions: utils.convertExceptions, convertExceptions: utils.convertExceptions,
convertKeysFromSnakeCaseToCamelCase: convertKeysFromSnakeCaseToCamelCase:
utils.convertKeysFromSnakeCaseToCamelCase, utils.convertKeysFromSnakeCaseToCamelCase,

View File

@@ -66,6 +66,16 @@ function composeAsync(wrapper: Wrapper, callback: Callback): Callback {
}; };
} }
function convertErrors(callback: () => void): () => void {
return function(error, data) {
if (error && !(error instanceof errors.RippleError)) {
callback(new errors.RippleError(error));
} else {
callback(error, data);
}
};
}
function convertExceptions<T>(f: () => T): () => T { function convertExceptions<T>(f: () => T): () => T {
return function() { return function() {
try { try {
@@ -105,6 +115,7 @@ module.exports = {
composeAsync, composeAsync,
wrapCatch, wrapCatch,
convertExceptions, convertExceptions,
convertErrors,
convertKeysFromSnakeCaseToCamelCase, convertKeysFromSnakeCaseToCamelCase,
promisify promisify
}; };

View File

@@ -5,6 +5,7 @@ const utils = require('./utils');
const removeUndefined = require('./parse/utils').removeUndefined; const removeUndefined = require('./parse/utils').removeUndefined;
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
type AccountData = { type AccountData = {
Sequence: number, Sequence: number,
@@ -66,10 +67,10 @@ function getAccountInfoAsync(account: string, options: AccountInfoOptions,
}; };
this.remote.requestAccountInfo(request, this.remote.requestAccountInfo(request,
composeAsync(formatAccountInfo, callback)); composeAsync(formatAccountInfo, convertErrors(callback)));
} }
function getAccountInfo(account: string, options: AccountInfoOptions={} function getAccountInfo(account: string, options: AccountInfoOptions = {}
): Promise<AccountInfoResponse> { ): Promise<AccountInfoResponse> {
return utils.promisify(getAccountInfoAsync).call(this, account, options); return utils.promisify(getAccountInfoAsync).call(this, account, options);
} }

View File

@@ -6,6 +6,7 @@ const utils = require('./utils');
const getTrustlines = require('./trustlines'); const getTrustlines = require('./trustlines');
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
function getTrustlineBalanceAmount(trustline) { function getTrustlineBalanceAmount(trustline) {
return { return {
@@ -39,10 +40,10 @@ function getBalancesAsync(account, options, callback) {
async.parallel({ async.parallel({
xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion), xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion),
trustlines: _.partial(getTrustlinesAsync.bind(this), account, options) trustlines: _.partial(getTrustlinesAsync.bind(this), account, options)
}, composeAsync(formatBalances, callback)); }, composeAsync(formatBalances, convertErrors(callback)));
} }
function getBalances(account: string, options={}) { function getBalances(account: string, options = {}) {
return utils.promisify(getBalancesAsync).call(this, account, options); return utils.promisify(getBalancesAsync).call(this, account, options);
} }

View File

@@ -3,6 +3,7 @@
const utils = require('./utils'); const utils = require('./utils');
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseLedger = require('./parse/ledger'); const parseLedger = require('./parse/ledger');
function getLedgerAsync(options, callback) { function getLedgerAsync(options, callback) {
@@ -16,10 +17,11 @@ function getLedgerAsync(options, callback) {
}; };
this.remote.requestLedger(request, this.remote.requestLedger(request,
composeAsync(response => parseLedger(response.ledger), callback)); composeAsync(response => parseLedger(response.ledger),
convertErrors(callback)));
} }
function getLedger(options={}) { function getLedger(options = {}) {
return utils.promisify(getLedgerAsync).call(this, options); return utils.promisify(getLedgerAsync).call(this, options);
} }

View File

@@ -3,9 +3,10 @@
const _ = require('lodash'); const _ = require('lodash');
const async = require('async'); const async = require('async');
const utils = require('./utils'); const utils = require('./utils');
const parseOrderbookOrder = require('./parse/orderbook-order');
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseOrderbookOrder = require('./parse/orderbook-order');
// account is to specify a "perspective", which affects which unfunded offers // account is to specify a "perspective", which affects which unfunded offers
// are returned // are returned
@@ -18,7 +19,7 @@ function getBookOffers(remote, account, ledgerVersion, limit,
ledger: ledgerVersion || 'validated', ledger: ledgerVersion || 'validated',
limit: limit, limit: limit,
taker: account taker: account
}), composeAsync(data => data.offers, callback)); }), composeAsync(data => data.offers, convertErrors(callback)));
} }
function isSameIssue(a, b) { function isSameIssue(a, b) {
@@ -77,7 +78,7 @@ function getOrderbookAsync(account, orderbook, options, callback) {
callback)); callback));
} }
function getOrderbook(account: string, orderbook: Object, options={}) { function getOrderbook(account: string, orderbook: Object, options = {}) {
return utils.promisify(getOrderbookAsync).call(this, return utils.promisify(getOrderbookAsync).call(this,
account, orderbook, options); account, orderbook, options);
} }

View File

@@ -4,10 +4,11 @@ const _ = require('lodash');
const utils = require('./utils'); const utils = require('./utils');
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseAccountOrder = require('./parse/account-order'); const parseAccountOrder = require('./parse/account-order');
function requestAccountOffers(remote, address, ledgerVersion, options, function requestAccountOffers(remote, address, ledgerVersion, marker, limit,
marker, limit, callback callback
) { ) {
remote.requestAccountOffers({ remote.requestAccountOffers({
account: address, account: address,
@@ -18,7 +19,7 @@ function requestAccountOffers(remote, address, ledgerVersion, options,
composeAsync((data) => ({ composeAsync((data) => ({
marker: data.marker, marker: data.marker,
results: data.offers.map(_.partial(parseAccountOrder, address)) results: data.offers.map(_.partial(parseAccountOrder, address))
}), callback)); }), convertErrors(callback)));
} }
function getOrdersAsync(account, options, callback) { function getOrdersAsync(account, options, callback) {
@@ -28,13 +29,13 @@ function getOrdersAsync(account, options, callback) {
const ledgerVersion = options.ledgerVersion const ledgerVersion = options.ledgerVersion
|| this.remote.getLedgerSequence(); || this.remote.getLedgerSequence();
const getter = _.partial(requestAccountOffers, this.remote, account, const getter = _.partial(requestAccountOffers, this.remote, account,
ledgerVersion, options); ledgerVersion);
utils.getRecursive(getter, options.limit, utils.getRecursive(getter, options.limit,
composeAsync((orders) => _.sortBy(orders, composeAsync((orders) => _.sortBy(orders,
(order) => order.properties.sequence), callback)); (order) => order.properties.sequence), callback));
} }
function getOrders(account: string, options={}) { function getOrders(account: string, options = {}) {
return utils.promisify(getOrdersAsync).call(this, account, options); return utils.promisify(getOrdersAsync).call(this, account, options);
} }

View File

@@ -8,6 +8,7 @@ const validate = utils.common.validate;
const parsePathfind = require('./parse/pathfind'); const parsePathfind = require('./parse/pathfind');
const NotFoundError = utils.common.errors.NotFoundError; const NotFoundError = utils.common.errors.NotFoundError;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
type PathFindParams = { type PathFindParams = {
src_currencies?: Array<string>, src_account: string, dst_amount: string, src_currencies?: Array<string>, src_account: string, dst_amount: string,
@@ -47,7 +48,7 @@ function requestPathFind(remote, pathfind: PathFind, callback) {
} }
remote.createPathFind(params, remote.createPathFind(params,
composeAsync(_.partial(addParams, params), callback)); composeAsync(_.partial(addParams, params), convertErrors(callback)));
} }
function addDirectXrpPath(paths, xrpBalance) { function addDirectXrpPath(paths, xrpBalance) {

View File

@@ -6,6 +6,7 @@ const validate = utils.common.validate;
const parseFields = require('./parse/fields'); const parseFields = require('./parse/fields');
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const AccountFlags = utils.common.constants.AccountFlags; const AccountFlags = utils.common.constants.AccountFlags;
const convertErrors = utils.common.convertErrors;
function parseFlags(value) { function parseFlags(value) {
const settings = {}; const settings = {};
@@ -34,10 +35,10 @@ function getSettingsAsync(account, options, callback) {
}; };
this.remote.requestAccountInfo(request, this.remote.requestAccountInfo(request,
composeAsync(formatSettings, callback)); composeAsync(formatSettings, convertErrors(callback)));
} }
function getSettings(account: string, options={}) { function getSettings(account: string, options = {}) {
return utils.promisify(getSettingsAsync).call(this, account, options); return utils.promisify(getSettingsAsync).call(this, account, options);
} }

View File

@@ -6,6 +6,7 @@ const utils = require('./utils');
const parseTransaction = require('./parse/transaction'); const parseTransaction = require('./parse/transaction');
const validate = utils.common.validate; const validate = utils.common.validate;
const errors = utils.common.errors; const errors = utils.common.errors;
const convertErrors = utils.common.convertErrors;
const RippleError = require('../../core/rippleerror').RippleError; const RippleError = require('../../core/rippleerror').RippleError;
import type {Remote} from '../../core/remote'; import type {Remote} from '../../core/remote';
@@ -69,9 +70,9 @@ function getTransactionAsync(identifier: string, options: TransactionOptions,
} else if (!error && tx && !isTransactionInRange(tx, options)) { } else if (!error && tx && !isTransactionInRange(tx, options)) {
callback(new errors.NotFoundError('Transaction not found')); callback(new errors.NotFoundError('Transaction not found'));
} else if (error) { } else if (error) {
callback(error); convertErrors(callback)(error);
} else if (!tx) { } else if (!tx) {
callback(new Error('Internal error')); callback(new errors.ApiError('Internal error'));
} else { } else {
callback(error, parseTransaction(tx)); callback(error, parseTransaction(tx));
} }
@@ -85,7 +86,7 @@ function getTransactionAsync(identifier: string, options: TransactionOptions,
} }
function getTransaction(identifier: string, function getTransaction(identifier: string,
options: TransactionOptions={} options: TransactionOptions = {}
): Promise<GetTransactionResponse> { ): Promise<GetTransactionResponse> {
return utils.promisify(getTransactionAsync).call(this, identifier, options); return utils.promisify(getTransactionAsync).call(this, identifier, options);
} }

View File

@@ -7,6 +7,7 @@ const parseTransaction = require('./parse/transaction');
const getTransaction = require('./transaction'); const getTransaction = require('./transaction');
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync; const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
function parseAccountTxTransaction(tx) { function parseAccountTxTransaction(tx) {
// rippled uses a different response format for 'account_tx' than 'tx' // rippled uses a different response format for 'account_tx' than 'tx'
@@ -55,6 +56,17 @@ function orderFilter(options, tx) {
utils.compareTransactions(tx, options.startTx) < 0); utils.compareTransactions(tx, options.startTx) < 0);
} }
function formatPartialResponse(address, options, data) {
return {
marker: data.marker,
results: data.transactions
.filter((tx) => tx.validated)
.map(parseAccountTxTransaction)
.filter(_.partial(transactionFilter, address, options))
.filter(_.partial(orderFilter, options))
};
}
function getAccountTx(remote, address, options, marker, limit, callback) { function getAccountTx(remote, address, options, marker, limit, callback) {
const params = { const params = {
account: address, account: address,
@@ -66,16 +78,9 @@ function getAccountTx(remote, address, options, marker, limit, callback) {
marker: marker marker: marker
}; };
remote.requestAccountTx(params, (error, data) => { remote.requestAccountTx(params,
return error ? callback(error) : callback(null, { composeAsync(_.partial(formatPartialResponse, address, options),
marker: data.marker, convertErrors(callback)));
results: data.transactions
.filter((tx) => tx.validated)
.map(parseAccountTxTransaction)
.filter(_.partial(transactionFilter, address, options))
.filter(_.partial(orderFilter, options))
});
});
} }
function checkForLedgerGaps(remote, options, transactions) { function checkForLedgerGaps(remote, options, transactions) {
@@ -131,7 +136,7 @@ function getTransactionsAsync(account, options, callback) {
} }
} }
function getTransactions(account: string, options={}) { function getTransactions(account: string, options = {}) {
return utils.promisify(getTransactionsAsync).call(this, account, options); return utils.promisify(getTransactionsAsync).call(this, account, options);
} }

View File

@@ -3,12 +3,22 @@
const _ = require('lodash'); const _ = require('lodash');
const utils = require('./utils'); const utils = require('./utils');
const validate = utils.common.validate; const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseAccountTrustline = require('./parse/account-trustline'); const parseAccountTrustline = require('./parse/account-trustline');
function currencyFilter(currency, trustline) { function currencyFilter(currency, trustline) {
return currency === null || trustline.specification.currency === currency; return currency === null || trustline.specification.currency === currency;
} }
function formatResponse(options, data) {
return {
marker: data.marker,
results: data.lines.map(parseAccountTrustline)
.filter(_.partial(currencyFilter, options.currency || null))
};
}
function getAccountLines(remote, address, ledgerVersion, options, marker, limit, function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
callback callback
) { ) {
@@ -20,14 +30,9 @@ function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
peer: options.counterparty peer: options.counterparty
}; };
remote.requestAccountLines(requestOptions, (error, data) => { remote.requestAccountLines(requestOptions,
return error ? callback(error) : composeAsync(_.partial(formatResponse, options),
callback(null, { convertErrors(callback)));
marker: data.marker,
results: data.lines.map(parseAccountTrustline)
.filter(_.partial(currencyFilter, options.currency || null))
});
});
} }
function getTrustlinesAsync(account: string, options: {currency: string, function getTrustlinesAsync(account: string, options: {currency: string,
@@ -44,7 +49,7 @@ function getTrustlinesAsync(account: string, options: {currency: string,
utils.getRecursive(getter, options.limit, callback); utils.getRecursive(getter, options.limit, callback);
} }
function getTrustlines(account: string, options={}) { function getTrustlines(account: string, options = {}) {
return utils.promisify(getTrustlinesAsync).call(this, account, options); return utils.promisify(getTrustlinesAsync).call(this, account, options);
} }

View File

@@ -3,16 +3,17 @@
const utils = require('./utils'); const utils = require('./utils');
const validate = utils.common.validate; const validate = utils.common.validate;
const Request = utils.common.core.Request; const Request = utils.common.core.Request;
const convertErrors = utils.common.convertErrors;
function submitAsync(txBlob: string, callback: (err: any, data: any) => void): function submitAsync(txBlob: string, callback: (err: any, data: any) => void
void { ): void {
validate.blob(txBlob); validate.blob(txBlob);
const request = new Request(this.remote, 'submit'); const request = new Request(this.remote, 'submit');
request.message.tx_blob = txBlob; request.message.tx_blob = txBlob;
request.request(null, request.request(null,
utils.common.composeAsync( utils.common.composeAsync(
data => utils.common.convertKeysFromSnakeCaseToCamelCase(data), data => utils.common.convertKeysFromSnakeCaseToCamelCase(data),
callback)); convertErrors(callback)));
} }
function submit(txBlob: string) { function submit(txBlob: string) {

View File

@@ -1816,10 +1816,7 @@ Remote.prototype.createPathFind = function(options, callback) {
callback(null, data); callback(null, data);
} }
}); });
pathFind.on('error', (error) => { pathFind.on('error', callback);
pathFind.close();
callback(error);
});
} }
this._cur_path_find = pathFind; this._cur_path_find = pathFind;

View File

@@ -63,6 +63,10 @@ Request.prototype.request = function(servers, callback) {
return this; return this;
}; };
function isResponseNotError(res) {
return typeof res === 'object' && !res.hasOwnProperty('error');
}
/** /**
* Broadcast request to all servers, filter responses if a function is * Broadcast request to all servers, filter responses if a function is
* provided. Return first response that satisfies the filter. Pre-filter * provided. Return first response that satisfies the filter. Pre-filter
@@ -74,16 +78,17 @@ Request.prototype.request = function(servers, callback) {
* @param [Function] fn * @param [Function] fn
*/ */
Request.prototype.filter = Request.prototype.filter =
Request.prototype.addFilter = Request.prototype.addFilter =
Request.prototype.broadcast = function(filterFn = Boolean) { Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
const self = this; const self = this;
if (!this.requested) { if (!this.requested) {
// Defer until requested, and prevent the normal request() from executing // Defer until requested, and prevent the normal request() from executing
this.once('before', function() { this.once('before', function() {
self.requested = true; self.requested = true;
self.broadcast(filterFn); self.broadcast(isResponseSuccess);
}); });
return this; return this;
} }
@@ -111,7 +116,7 @@ Request.prototype.broadcast = function(filterFn = Boolean) {
// Listen for proxied success/error event and apply filter // Listen for proxied success/error event and apply filter
self.once('proposed', function(res) { self.once('proposed', function(res) {
lastResponse = res; lastResponse = res;
callback(filterFn(res)); callback(isResponseSuccess(res));
}); });
return server._request(self); return server._request(self);

View File

@@ -1,6 +1,5 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const assert = require('assert'); const assert = require('assert');
const util = require('util'); const util = require('util');
@@ -130,9 +129,8 @@ function Server(remote, opts_) {
self._updateScore('ledgerclose', ledger); self._updateScore('ledgerclose', ledger);
}); });
/* eslint-disable no-unused-vars */
this.on('response_ping', function onPingResponse(message, request) { this.on('response_ping', function onPingResponse(message, request) {
/* eslint-enable no-unused-vars */ _.noop(message);
self._updateScore('response', request); self._updateScore('response', request);
}); });

View File

@@ -391,7 +391,7 @@ describe('RippleAPI', function() {
return this.api.getTransactions(address, options).then(() => { return this.api.getTransactions(address, options).then(() => {
assert(false, 'Should throw RippleError'); assert(false, 'Should throw RippleError');
}).catch(error => { }).catch(error => {
assert(error instanceof common.core.RippleError); assert(error instanceof this.api.errors.RippleError);
}); });
}); });
@@ -552,6 +552,14 @@ describe('RippleAPI', function() {
}); });
}); });
it('getPaths - error: srcActNotFound', function() {
const pathfind = _.assign({}, requests.getPaths.normal,
{source: {address: addresses.NOTFOUND}});
return this.api.getPaths(pathfind).catch(error => {
assert(error instanceof this.api.errors.RippleError);
});
});
it('getLedgerVersion', function() { it('getLedgerVersion', function() {
assert.strictEqual(this.api.getLedgerVersion(), 8819951); assert.strictEqual(this.api.getLedgerVersion(), 8819951);
}); });

View File

@@ -19,7 +19,8 @@ module.exports = {
path_find: { path_find: {
generate: require('./path-find'), generate: require('./path-find'),
sendUSD: require('./path-find-send-usd'), sendUSD: require('./path-find-send-usd'),
XrpToXrp: require('./path-find-xrp-to-xrp') XrpToXrp: require('./path-find-xrp-to-xrp'),
srcActNotFound: require('./path-find-srcActNotFound')
}, },
tx: { tx: {
Payment: require('./tx/payment.json'), Payment: require('./tx/payment.json'),

View File

@@ -0,0 +1,20 @@
{
"error": "srcActNotFound",
"error_code": 58,
"error_message": "Source account not found.",
"id": 0,
"request": {
"command": "path_find",
"destination_account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"destination_amount": {
"currency": "USD",
"issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"value": "5"
},
"id": 1,
"source_account": "rajTAg3hon5Lcu1RxQQPxTgHvqfhc1EaUS",
"subcommand": "create"
},
"status": "error",
"type": "response"
}

View File

@@ -18,7 +18,7 @@ function isBTC(json) {
return json === 'BTC' || json === '0000000000000000000000004254430000000000'; return json === 'BTC' || json === '0000000000000000000000004254430000000000';
} }
function createResponse(request, response, overrides={}) { function createResponse(request, response, overrides = {}) {
const result = _.assign({}, response.result, overrides); const result = _.assign({}, response.result, overrides);
const change = response.result && !_.isEmpty(overrides) ? const change = response.result && !_.isEmpty(overrides) ?
{id: request.id, result: result} : {id: request.id}; {id: request.id, result: result} : {id: request.id};
@@ -253,7 +253,9 @@ module.exports = function(port) {
if (request.subcommand === 'close') { if (request.subcommand === 'close') {
return; return;
} }
if (request.source_account === addresses.OTHER_ACCOUNT) { if (request.source_account === addresses.NOTFOUND) {
response = createResponse(request, fixtures.path_find.srcActNotFound);
} else if (request.source_account === addresses.OTHER_ACCOUNT) {
response = createResponse(request, fixtures.path_find.sendUSD); response = createResponse(request, fixtures.path_find.sendUSD);
} else if (request.source_account === addresses.THIRD_ACCOUNT) { } else if (request.source_account === addresses.THIRD_ACCOUNT) {
response = createResponse(request, fixtures.path_find.XrpToXrp, { response = createResponse(request, fixtures.path_find.XrpToXrp, {