From b1dbdc03dd27528ef462fa4177b9728d4e313f9f Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Thu, 24 Sep 2015 17:29:42 -0700 Subject: [PATCH 01/22] Decouple UInt160 from account.js --- npm-shrinkwrap.json | 56 ++++++++++++++++++++++++++++++---- package.json | 2 +- src/core/account.js | 38 ++++++++++------------- src/core/transactionmanager.js | 2 +- 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0f307cd2..d3e28395 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,8 +9,8 @@ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" }, "babel-runtime": { - "version": "5.8.24", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.24.tgz", + "version": "5.8.25", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.25.tgz", "dependencies": { "core-js": { "version": "1.1.4", @@ -117,12 +117,12 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz" }, "ripple-address-codec": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-1.6.0.tgz", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz", "dependencies": { "x-address-codec": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.6.0.tgz", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.0.tgz", "dependencies": { "base-x": { "version": "1.0.1", @@ -149,6 +149,22 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } + }, + "ripple-address-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-1.6.0.tgz", + "dependencies": { + "x-address-codec": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.6.0.tgz", + "dependencies": { + "base-x": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz" + } + } + } + } } } }, @@ -168,6 +184,20 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz", "dependencies": { + "bufferutil": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz", + "dependencies": { + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" + }, + "nan": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz" + } + } + }, "options": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz" @@ -175,6 +205,20 @@ "ultron": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz" + }, + "utf-8-validate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.1.0.tgz", + "dependencies": { + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" + }, + "nan": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz" + } + } } } } diff --git a/package.json b/package.json index d422f3cc..d1046bd7 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "is-my-json-valid": "^2.12.2", "lodash": "^3.1.0", "lru-cache": "~2.5.0", - "ripple-address-codec": "^1.6.0", + "ripple-address-codec": "^2.0.1", "ripple-keypairs": "^0.9.0", "ripple-lib-transactionparser": "^0.5.1", "ripple-lib-value": "0.1.0", diff --git a/src/core/account.js b/src/core/account.js index 3e3267bf..a7340c1e 100644 --- a/src/core/account.js +++ b/src/core/account.js @@ -17,7 +17,7 @@ const util = require('util'); const {deriveAddress} = require('ripple-keypairs'); const {EventEmitter} = require('events'); const {TransactionManager} = require('./transactionmanager'); -const {UInt160} = require('./uint160'); +const {isValidAddress} = require('ripple-address-codec'); /** * @constructor Account @@ -25,14 +25,13 @@ const {UInt160} = require('./uint160'); * @param {String} account */ -function Account(remote, account) { +function Account(remote, address) { EventEmitter.call(this); const self = this; this._remote = remote; - this._account = UInt160.from_json(account); - this._account_id = this._account.to_json(); + this._address = address; this._subs = 0; // Ledger entry object @@ -43,7 +42,7 @@ function Account(remote, account) { if (_.includes(Account.subscribeEvents, type)) { if (!self._subs && self._remote._connected) { self._remote.requestSubscribe() - .addAccount(self._account_id) + .addAccount(self._address) .broadcast().request(); } self._subs += 1; @@ -57,7 +56,7 @@ function Account(remote, account) { self._subs -= 1; if (!self._subs && self._remote._connected) { self._remote.requestUnsubscribe() - .addAccount(self._account_id) + .addAccount(self._address) .broadcast().request(); } } @@ -66,8 +65,8 @@ function Account(remote, account) { this.on('removeListener', listenerRemoved); function attachAccount(request) { - if (self._account.is_valid() && self._subs) { - request.addAccount(self._account_id); + if (isValidAddress(self._address) && self._subs) { + request.addAccount(self._address); } } @@ -81,7 +80,7 @@ function Account(remote, account) { let changed = false; transaction.mmeta.each(function(an) { - const isAccount = an.fields.Account === self._account_id; + const isAccount = an.fields.Account === self._address; const isAccountRoot = isAccount && (an.entryType === 'AccountRoot'); if (isAccountRoot) { @@ -111,7 +110,7 @@ util.inherits(Account, EventEmitter); Account.subscribeEvents = ['transaction', 'entry']; Account.prototype.toJson = function() { - return this._account.to_json(); + return this._address; }; /** @@ -121,7 +120,7 @@ Account.prototype.toJson = function() { */ Account.prototype.isValid = function() { - return this._account.is_valid(); + return isValidAddress(this._address); }; /** @@ -131,7 +130,7 @@ Account.prototype.isValid = function() { */ Account.prototype.getInfo = function(callback) { - return this._remote.requestAccountInfo({account: this._account_id}, callback); + return this._remote.requestAccountInfo({account: this._address}, callback); }; /** @@ -210,7 +209,7 @@ Account.prototype.lines = function(callback_) { } } - this._remote.requestAccountLines({account: this._account_id}, accountLines); + this._remote.requestAccountLines({account: this._address}, accountLines); return this; }; @@ -275,7 +274,7 @@ Account.prototype.notifyTx = function(transaction) { return; } - const isThisAccount = (account === this._account_id); + const isThisAccount = (account === this._address); this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction); @@ -331,7 +330,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) { // Catch the case of unfunded accounts if (!account_info_res) { - if (public_key_as_uint160 === self._account_id) { + if (public_key_as_uint160 === self._address) { async_callback(null, true); } else { async_callback(null, false); @@ -373,15 +372,10 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) { * @returns {RippleAddress} Ripple Address */ Account._publicKeyToAddress = function(public_key) { - // Based on functions in /src/js/ripple/keypair.js - function hexToUInt160(publicKey) { - return deriveAddress(publicKey); - } - - if (UInt160.is_valid(public_key)) { + if (isValidAddress(public_key)) { return public_key; } else if (/^[0-9a-fA-F]+$/.test(public_key)) { - return hexToUInt160(public_key); + return deriveAddress(public_key); } else { // eslint-disable-line no-else-return throw new Error('Public key is invalid. Must be a UInt160 or a hex string'); } diff --git a/src/core/transactionmanager.js b/src/core/transactionmanager.js index 3406e30f..fb5c241d 100644 --- a/src/core/transactionmanager.js +++ b/src/core/transactionmanager.js @@ -21,7 +21,7 @@ function TransactionManager(account) { const self = this; this._account = account; - this._accountID = account._account_id; + this._accountID = account._address; this._remote = account._remote; this._nextSequence = undefined; this._maxFee = this._remote.max_fee; From c6805b9f0d1c4ba688bb9cf730901745711be869 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Fri, 25 Sep 2015 10:41:21 -0700 Subject: [PATCH 02/22] Decouple UInt160 from amount.js --- src/core/account.js | 4 ++- src/core/amount.js | 55 ++++++++++++++++--------------------- src/core/constants.js | 6 ++++ src/core/meta.js | 10 ++++--- src/core/orderbookutils.js | 3 +- src/core/serializedtypes.js | 2 +- src/core/transaction.js | 3 +- 7 files changed, 43 insertions(+), 40 deletions(-) create mode 100644 src/core/constants.js diff --git a/src/core/account.js b/src/core/account.js index a7340c1e..1af60f19 100644 --- a/src/core/account.js +++ b/src/core/account.js @@ -381,6 +381,8 @@ Account._publicKeyToAddress = function(public_key) { } }; -exports.Account = Account; +module.exports = { + Account +}; // vim:sw=2:sts=2:ts=8:et diff --git a/src/core/amount.js b/src/core/amount.js index 0a7a1224..f2959d4d 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -6,9 +6,10 @@ const assert = require('assert'); const extend = require('extend'); const utils = require('./utils'); -const UInt160 = require('./uint160').UInt160; const Currency = require('./currency').Currency; const {XRPValue, IOUValue} = require('ripple-lib-value'); +const {isValidAddress} = require('ripple-address-codec'); +const {ACCOUNT_ONE, ACCOUNT_ZERO} = require('./constants'); type Value = XRPValue | IOUValue; @@ -21,7 +22,7 @@ function Amount(value = new XRPValue(NaN)) { this._value = value; this._is_native = true; // Default to XRP. Only valid if value is not NaN. this._currency = new Currency(); - this._issuer = new UInt160(); + this._issuer = 'NaN'; } /** @@ -105,7 +106,7 @@ Amount.NaN = function() { }; Amount.from_components_unsafe = function(value: Value, currency: Currency, - issuer: UInt160, isNative: boolean + issuer: string, isNative: boolean ) { const result = new Amount(value); result._is_native = isNative; @@ -402,7 +403,7 @@ Amount.prototype.equals = function(d, ignore_issuer) { && this._is_native === d._is_native && this._value.equals(d._value) && (this._is_native || (this._currency.equals(d._currency) - && (ignore_issuer || this._issuer.equals(d._issuer)))); + && (ignore_issuer || this._issuer === d._issuer))); }; // True if Amounts are valid and both native or non-native. @@ -428,9 +429,8 @@ Amount.prototype.is_valid = function() { }; Amount.prototype.is_valid_full = function() { - return this.is_valid() - && this._currency.is_valid() - && this._issuer.is_valid(); + return this.is_valid() && this._currency.is_valid() + && isValidAddress(this._issuer) && this._issuer !== ACCOUNT_ZERO; }; Amount.prototype.is_zero = function() { @@ -532,7 +532,7 @@ Amount.prototype.parse_human = function(j, options) { }; Amount.prototype.parse_issuer = function(issuer) { - this._issuer = UInt160.from_json(issuer); + this._issuer = issuer; return this; }; @@ -583,7 +583,7 @@ function(quality, counterCurrency, counterIssuer, opts) { const offset = parseInt(offset_hex, 16) - 100; this._currency = Currency.from_json(counterCurrency); - this._issuer = UInt160.from_json(counterIssuer); + this._issuer = counterIssuer; this._is_native = this._currency.is_native(); if (this._is_native && baseCurrency.is_native()) { @@ -643,7 +643,7 @@ function(quality, counterCurrency, counterIssuer, opts) { Amount.prototype.parse_number = function(n) { this._is_native = false; this._currency = Currency.from_json(1); - this._issuer = UInt160.from_json(1); + this._issuer = ACCOUNT_ONE; this._set_value(new IOUValue(n)); return this; }; @@ -659,15 +659,15 @@ Amount.prototype.parse_json = function(j) { if (m) { this._currency = Currency.from_json(m[2]); if (m[3]) { - this._issuer = UInt160.from_json(m[3]); + this._issuer = m[3]; } else { - this._issuer = UInt160.from_json('1'); + this._issuer = 'NaN'; } this.parse_value(m[1]); } else { this.parse_native(j); this._currency = Currency.from_json('0'); - this._issuer = UInt160.from_json('0'); + this._issuer = ACCOUNT_ZERO; } break; @@ -686,9 +686,10 @@ Amount.prototype.parse_json = function(j) { // Parse the passed value to sanitize and copy it. this._currency.parse_json(j.currency, true); // Never XRP. - if (typeof j.issuer === 'string') { - this._issuer.parse_json(j.issuer); + if (typeof j.issuer !== 'string') { + throw new Error('issuer must be a string'); } + this._issuer = j.issuer; this.parse_value(j.value); } @@ -736,12 +737,7 @@ Amount.prototype.set_currency = function(c) { }; Amount.prototype.set_issuer = function(issuer) { - if (issuer instanceof UInt160) { - this._issuer = issuer; - } else { - this._issuer = UInt160.from_json(issuer); - } - + this._issuer = issuer; return this; }; @@ -939,7 +935,7 @@ Amount.prototype.to_human_full = function(options) { const opts = options || {}; const value = this.to_human(opts); const currency = this._currency.to_human(); - const issuer = this._issuer.to_json(opts); + const issuer = this._issuer; const base = value + '/' + currency; return this.is_native() ? base : (base + '/' + issuer); }; @@ -955,21 +951,21 @@ Amount.prototype.to_json = function() { this._currency.to_hex() : this._currency.to_json() }; - if (this._issuer.is_valid()) { - amount_json.issuer = this._issuer.to_json(); + if (isValidAddress(this._issuer)) { + amount_json.issuer = this._issuer; } return amount_json; }; -Amount.prototype.to_text_full = function(opts) { +Amount.prototype.to_text_full = function() { if (!this.is_valid()) { return 'NaN'; } return this._is_native ? this.to_human() + '/XRP' : this.to_text() + '/' + this._currency.to_json() - + '/' + this._issuer.to_json(opts); + + '/' + this._issuer; }; // For debugging. @@ -998,11 +994,8 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) { if (!this._currency.equals(d._currency)) { return 'Non-XRP currency differs.'; } - if (!ignore_issuer && !this._issuer.equals(d._issuer)) { - return 'Non-XRP issuer differs: ' - + d._issuer.to_json() - + '/' - + this._issuer.to_json(); + if (!ignore_issuer && this._issuer !== d._issuer) { + return 'Non-XRP issuer differs: ' + d._issuer + '/' + this._issuer; } } }; diff --git a/src/core/constants.js b/src/core/constants.js new file mode 100644 index 00000000..3adbdbf9 --- /dev/null +++ b/src/core/constants.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = { + ACCOUNT_ZERO: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp', + ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji' +}; diff --git a/src/core/meta.js b/src/core/meta.js index c4f9b781..617931f4 100644 --- a/src/core/meta.js +++ b/src/core/meta.js @@ -2,6 +2,8 @@ var extend = require('extend'); var utils = require('./utils'); var UInt160 = require('./uint160').UInt160; var Amount = require('./amount').Amount; +var ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO; +var {isValidAddress} = require('ripple-address-codec'); /** * Meta data processing facility @@ -154,8 +156,8 @@ Meta.prototype.getAffectedAccounts = function(from) { } else if (~Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName)) { var amount = Amount.from_json(field); var issuer = amount.issuer(); - if (issuer.is_valid() && !issuer.is_zero()) { - accounts.push(issuer.to_json()); + if (isValidAddress(issuer) && issuer !== ACCOUNT_ZERO) { + accounts.push(issuer); } } } @@ -186,11 +188,11 @@ Meta.prototype.getAffectedBooks = function() { var paysKey = pays.currency().to_json(); if (getsKey !== 'XRP') { - getsKey += '/' + gets.issuer().to_json(); + getsKey += '/' + gets.issuer(); } if (paysKey !== 'XRP') { - paysKey += '/' + pays.issuer().to_json(); + paysKey += '/' + pays.issuer(); } var key = getsKey + ':' + paysKey; diff --git a/src/core/orderbookutils.js b/src/core/orderbookutils.js index ec9eb90e..462bc333 100644 --- a/src/core/orderbookutils.js +++ b/src/core/orderbookutils.js @@ -29,8 +29,7 @@ function createAmount(value, currency_, counterparty_) { Currency.from_json(currency_); const counterparty = counterparty_ instanceof UInt160 ? - counterparty_ : - UInt160.from_json(counterparty_); + counterparty_.to_json() : counterparty_; return Amount.from_components_unsafe(new IOUValue(value), currency, counterparty, false); diff --git a/src/core/serializedtypes.js b/src/core/serializedtypes.js index f8787d46..8f08964f 100644 --- a/src/core/serializedtypes.js +++ b/src/core/serializedtypes.js @@ -518,7 +518,7 @@ const STAmount = exports.Amount = new SerializedType({ STCurrency.serialize(so, currency, true); // Issuer (160-bit hash) - so.append(amount.issuer().to_bytes()); + so.append(UInt160.from_json(amount.issuer()).to_bytes()); } }, parse: function(so) { diff --git a/src/core/transaction.js b/src/core/transaction.js index 90e455fe..0a54b4cb 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -14,6 +14,7 @@ const SerializedObject = require('./serializedobject').SerializedObject; const RippleError = require('./rippleerror').RippleError; const hashprefixes = require('./hashprefixes'); const log = require('./log').internal.sub('transaction'); +const {isValidAddress} = require('ripple-address-codec'); /** * @constructor Transaction @@ -722,7 +723,7 @@ Transaction.prototype._setAmount = function(name, amount, options_) { if (!(isNative || parsedAmount.currency().is_valid())) { throw new Error(name + ' must have a valid currency'); } - if (!(isNative || parsedAmount.issuer().is_valid())) { + if (!(isNative || isValidAddress(parsedAmount.issuer()))) { throw new Error(name + ' must have a valid issuer'); } From c2ca37a790c474da4b38fb16a5a6f98bf49ebba0 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Fri, 25 Sep 2015 12:31:16 -0700 Subject: [PATCH 03/22] Fix lint errors in meta.js and serializedtypes.js --- src/core/meta.js | 101 ++++++++++++++++++------------------ src/core/serializedtypes.js | 11 ++-- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/core/meta.js b/src/core/meta.js index 617931f4..d1b026c3 100644 --- a/src/core/meta.js +++ b/src/core/meta.js @@ -1,9 +1,10 @@ -var extend = require('extend'); -var utils = require('./utils'); -var UInt160 = require('./uint160').UInt160; -var Amount = require('./amount').Amount; -var ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO; -var {isValidAddress} = require('ripple-address-codec'); +'use strict'; +const extend = require('extend'); +const utils = require('./utils'); +const UInt160 = require('./uint160').UInt160; +const Amount = require('./amount').Amount; +const ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO; +const {isValidAddress} = require('ripple-address-codec'); /** * Meta data processing facility @@ -13,8 +14,6 @@ var {isValidAddress} = require('ripple-address-codec'); */ function Meta(data) { - var self = this; - this.nodes = [ ]; if (typeof data !== 'object') { @@ -26,7 +25,7 @@ function Meta(data) { } data.AffectedNodes.forEach(this.addNode, this); -}; +} Meta.NODE_TYPES = [ 'CreatedNode', @@ -55,10 +54,10 @@ Meta.ACCOUNT_FIELDS = [ */ Meta.prototype.getNodeType = function(node) { - var result = null; + let result = null; - for (var i=0; i 254) { @@ -416,7 +417,8 @@ exports.Quality = new SerializedType({ value = new BigNumber(val); } - let hi = 0, lo = 0; + let hi = 0; + let lo = 0; const offset = value.e - 15; if (val !== 0) { @@ -483,7 +485,8 @@ const STAmount = exports.Amount = new SerializedType({ valueBytes[0] |= 0x40; } } else { - let hi = 0, lo = 0; + let hi = 0; + let lo = 0; // First bit: non-native hi |= 1 << 31; @@ -834,7 +837,7 @@ exports.STMemo = new SerializedType({ output.parsed_memo_data = convertHexToString(output.MemoData); } /* eslint-disable no-empty */ - } catch(e) { + } catch (e) { // empty // we'll fail in case the content does not match what the MemoFormat // described From 62a2d2ae3972f5678d7536fa2e4eec85d9c8980c Mon Sep 17 00:00:00 2001 From: Alan Cohen Date: Fri, 25 Sep 2015 20:18:36 -0700 Subject: [PATCH 04/22] Revert "Set default maxLedgerVersion to last closed ledger when requesting account_tx" This reverts commit 90b53002aa1e90bf7920f273806dc12ffbcd2107. account_tx operates against validated ledgers only --- src/api/ledger/transactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index 22511d90..8d2a4c14 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -71,7 +71,7 @@ function getAccountTx(remote, address, options, marker, limit, callback) { const params = { account: address, ledger_index_min: options.minLedgerVersion || -1, - ledger_index_max: options.maxLedgerVersion || remote.getLedgerSequence(), + ledger_index_max: options.maxLedgerVersion || -1, forward: options.earliestFirst, binary: options.binary, limit: utils.clamp(limit, 10, 400), From ed3b04ed6fd6420c0270d988ede7322c55924f39 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Mon, 28 Sep 2015 03:00:59 +0300 Subject: [PATCH 05/22] make sure that 'before' event emitted from Request only once --- src/core/request.js | 6 ++++-- test/request-test.js | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/core/request.js b/src/core/request.js index 239610df..93f2a08a 100644 --- a/src/core/request.js +++ b/src/core/request.js @@ -44,13 +44,15 @@ Request.prototype.request = function(servers, callback_) { const callback = typeof servers === 'function' ? servers : callback_; this.emit('before'); + + const wasRequested = this.requested; + this.requested = true; this.callback(callback); - if (this.requested) { + if (wasRequested) { return this; } - this.requested = true; this.on('error', function() {}); this.emit('request', this.remote); diff --git a/test/request-test.js b/test/request-test.js index 377aaefe..c1e1ee6b 100644 --- a/test/request-test.js +++ b/test/request-test.js @@ -1207,4 +1207,22 @@ describe('Request', function() { ] }); }); + + it('Emit "before" only once', function(done) { + const remote = new Remote(); + remote._connected = true; + + const request = new Request(remote, 'server_info'); + + let beforeCalled = 0; + + request.on('before', () => { + beforeCalled++; + }); + + request.request(function() {}); + assert.strictEqual(beforeCalled, 1); + done(); + }); + }); From e0cdd610dd5d83bbb2f8f94fef8de947320e15aa Mon Sep 17 00:00:00 2001 From: Alan Cohen Date: Fri, 25 Sep 2015 20:53:22 -0700 Subject: [PATCH 06/22] Set default max ledger to -1 when requesting account_tx -1 means up to the most recent available _validated_ ledger for ledger_index_max account_tx operates only on valided ledgers --- src/api/ledger/transactions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index 8d2a4c14..693591bc 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -70,7 +70,9 @@ function formatPartialResponse(address, options, data) { function getAccountTx(remote, address, options, marker, limit, callback) { const params = { account: address, + // -1 is equivalent to earliest available validated ledger ledger_index_min: options.minLedgerVersion || -1, + // -1 is equivalent to most recent available validated ledger ledger_index_max: options.maxLedgerVersion || -1, forward: options.earliestFirst, binary: options.binary, @@ -121,7 +123,7 @@ function getTransactionsAsync(account, options, callback) { validate.address(account); validate.getTransactionsOptions(options); - const defaults = {maxLedgerVersion: this.remote.getLedgerSequence()}; + const defaults = {maxLedgerVersion: -1}; if (options.start) { getTransaction.call(this, options.start).then(tx => { const ledgerVersion = tx.outcome.ledgerVersion; From 5217b66396de56870f7e38fd7747e6ce5d335817 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Tue, 29 Sep 2015 11:25:02 -0700 Subject: [PATCH 07/22] Fix pathfind queuing and add unit test --- src/core/remote.js | 28 ++++++++++++++-------------- test/api-test.js | 12 ++++++++++++ test/mock-rippled.js | 3 ++- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/core/remote.js b/src/core/remote.js index c39b437a..638def41 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -1803,9 +1803,20 @@ Remote.prototype.createPathFind = function(options, callback) { if (callback) { pathFind.on('update', (data) => { - if (data.full_reply) { - pathFind.close(); + if (data.full_reply && !data.closed) { + this._cur_path_find = null; callback(null, data); + // "A client can only have one pathfinding request open at a time. + // If another pathfinding request is already open on the same + // connection, the old request is automatically closed and replaced + // with the new request." + // - ripple.com/build/rippled-apis/#path-find-create + if (this._queued_path_finds.length > 0) { + const pathfind = this._queued_path_finds.shift(); + this.createPathFind(pathfind.options, pathfind.callback); + } else { + pathFind.close(); + } } }); pathFind.on('error', callback); @@ -2145,19 +2156,8 @@ Remote.prototype.requestPathFindCreate = function(options, callback) { Remote.prototype.requestPathFindClose = function(callback) { const request = new Request(this, 'path_find'); - request.message.subcommand = 'close'; - request.callback((error, data) => { - this._cur_path_find = null; - if (this._queued_path_finds.length > 0) { - const pathfind = this._queued_path_finds.shift(); - this.createPathFind(pathfind.options, pathfind.callback); - } - if (callback) { - callback(error, data); - } - }); - + request.callback(callback); return request; }; diff --git a/test/api-test.js b/test/api-test.js index 1dbf408e..9e2f4225 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -570,6 +570,18 @@ describe('RippleAPI', function() { _.partial(checkResult, responses.getPaths.XrpToUsd, 'getPaths')); }); + it('getPaths - queuing', function() { + return Promise.all([ + this.api.getPaths(requests.getPaths.normal), + this.api.getPaths(requests.getPaths.UsdToUsd), + this.api.getPaths(requests.getPaths.XrpToXrp) + ]).then(results => { + checkResult(responses.getPaths.XrpToUsd, 'getPaths', results[0]); + checkResult(responses.getPaths.UsdToUsd, 'getPaths', results[1]); + checkResult(responses.getPaths.XrpToXrp, 'getPaths', results[2]); + }); + }); + // @TODO // need decide what to do with currencies/XRP: // if add 'XRP' in currencies, then there will be exception in diff --git a/test/mock-rippled.js b/test/mock-rippled.js index bf662d05..e76f94ae 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -271,7 +271,8 @@ module.exports = function(port) { request.id, request.source_account, request.destination_account, request.destination_amount); } - conn.send(response); + // delay response to simulate calculation time so we can test queuing + setTimeout(() => conn.send(response), 20); }); return mock; From 60e2d10775e5af07833ae45d5b421baf128b5ae3 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Wed, 30 Sep 2015 06:29:07 +0300 Subject: [PATCH 08/22] throw error if Request.request called more than once --- src/core/request.js | 15 +++++++++------ test/request-test.js | 8 +++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/core/request.js b/src/core/request.js index 93f2a08a..ea9046d4 100644 --- a/src/core/request.js +++ b/src/core/request.js @@ -43,16 +43,19 @@ Request.prototype.request = function(servers, callback_) { const self = this; const callback = typeof servers === 'function' ? servers : callback_; + if (this.requested) { + throw new Error('Already requested'); + } + this.emit('before'); - - const wasRequested = this.requested; - this.requested = true; - this.callback(callback); - - if (wasRequested) { + // emit handler can set requested flag + if (this.requested) { return this; } + this.requested = true; + this.callback(callback); + this.on('error', function() {}); this.emit('request', this.remote); diff --git a/test/request-test.js b/test/request-test.js index c1e1ee6b..a606bba6 100644 --- a/test/request-test.js +++ b/test/request-test.js @@ -40,13 +40,12 @@ const SERVER_INFO = { }; describe('Request', function() { - it('Send request', function(done) { + it('Send request', function() { const remote = { request: function(req) { assert(req instanceof Request); assert.strictEqual(typeof req.message, 'object'); assert.strictEqual(req.message.command, 'server_info'); - done(); }, on: function() { }, @@ -60,7 +59,10 @@ describe('Request', function() { request.request(); // Should only request once - request.request(); + assert.throws(function() { + request.request(); + }, Error); + }); it('Send request -- filterRequest', function(done) { From bfe590d96d2ca80f1ac3c149e91a084d821e259f Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Mon, 14 Sep 2015 22:38:15 +0300 Subject: [PATCH 09/22] copy message from Error instance into RippleError --- src/core/rippleerror.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/core/rippleerror.js b/src/core/rippleerror.js index a9aed005..08d647f1 100644 --- a/src/core/rippleerror.js +++ b/src/core/rippleerror.js @@ -1,23 +1,28 @@ -var util = require('util'); -var extend = require('extend'); +const util = require('util'); +const _ = require('lodash'); -function RippleError(code, message) { - switch (typeof code) { - case 'object': - extend(this, code); - break; +function RippleError(code?: any, message?: string) { + if (code instanceof Error) { + this.result = code; + this.result_message = code.message; + } else { + switch (typeof code) { + case 'object': + _.extend(this, code); + break; - case 'string': - this.result = code; - this.result_message = message; - break; + case 'string': + this.result = code; + this.result_message = message; + break; + } } this.engine_result = this.result = (this.result || this.engine_result || this.error || 'Error'); this.engine_result_message = this.result_message = (this.result_message || this.engine_result_message || this.error_message || 'Error'); - this.result_message = this.message = (this.result_message); + this.message = this.result_message; - var stack; + let stack; if (!!Error.captureStackTrace) { Error.captureStackTrace(this, code || this); From 51e8f9a87abe42d6b19390400f34de48f2356368 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Mon, 14 Sep 2015 22:41:50 +0300 Subject: [PATCH 10/22] make Remote.getLedgerSequence asynchronous --- src/api/common/utils.js | 3 +- src/api/ledger/balances.js | 21 ++++++++-- src/api/ledger/orders.js | 17 ++++++-- src/api/ledger/transaction.js | 19 +++++++-- src/api/ledger/trustlines.js | 17 ++++++-- src/api/ledger/utils.js | 2 +- src/api/server/server.js | 32 ++++++++++---- src/api/transaction/utils.js | 78 +++++++++++++++++++++++------------ src/core/remote.js | 58 +++++++++++++++++++++++++- src/core/rippleerror.js | 19 ++++++--- test/api-test.js | 25 +++++++---- 11 files changed, 225 insertions(+), 66 deletions(-) diff --git a/src/api/common/utils.js b/src/api/common/utils.js index bb231ef6..30f49874 100644 --- a/src/api/common/utils.js +++ b/src/api/common/utils.js @@ -71,7 +71,8 @@ function composeAsync(wrapper: Wrapper, callback: Callback): Callback { function convertErrors(callback: Callback): () => void { return function(error, data) { if (error && !(error instanceof errors.RippleError)) { - const error_ = new errors.RippleError(error); + const message = _.get(error, ['remote', 'error_message'], error.message); + const error_ = new errors.RippleError(message); error_.data = data; callback(error_, data); } else if (error) { diff --git a/src/api/ledger/balances.js b/src/api/ledger/balances.js index d05704da..fba119a5 100644 --- a/src/api/ledger/balances.js +++ b/src/api/ledger/balances.js @@ -7,6 +7,9 @@ const getTrustlines = require('./trustlines'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; const convertErrors = utils.common.convertErrors; +import type {Remote} from '../../core/remote'; +import type {GetLedgerSequenceCallback} from '../../core/remote'; + function getTrustlineBalanceAmount(trustline) { return { @@ -31,14 +34,26 @@ function getTrustlinesAsync(account, options, callback) { .catch(callback); } +function getLedgerVersion(remote: Remote, optionValue?: number, + callback: GetLedgerSequenceCallback +) { + if (typeof optionValue === 'number') { + callback(null, optionValue); + } else { + remote.getLedgerSequence(callback); + } +} + + function getBalancesAsync(account, options, callback) { validate.address(account); validate.getBalancesOptions(options); - const ledgerVersion = options.ledgerVersion - || this.remote.getLedgerSequence(); async.parallel({ - xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion), + xrp: async.compose( + _.partial(utils.getXRPBalance, this.remote, account), + _.partial(getLedgerVersion, this.remote, options.ledgerVersion) + ), trustlines: _.partial(getTrustlinesAsync.bind(this), account, options) }, composeAsync(formatBalances, convertErrors(callback))); } diff --git a/src/api/ledger/orders.js b/src/api/ledger/orders.js index 9a765237..cfb507ec 100644 --- a/src/api/ledger/orders.js +++ b/src/api/ledger/orders.js @@ -26,10 +26,21 @@ function getOrdersAsync(account, options, callback) { validate.address(account); validate.getOrdersOptions(options); - const ledgerVersion = options.ledgerVersion - || this.remote.getLedgerSequence(); + if (!options.ledgerVersion) { + const self = this; + this.remote.getLedgerSequence((err, seq) => { + if (err) { + convertErrors(callback)(err); + return; + } + const newOptions = _.extend(options, {ledgerVersion: seq}); + getOrdersAsync.call(self, account, newOptions, callback); + }); + return; + } + const getter = _.partial(requestAccountOffers, this.remote, account, - ledgerVersion); + options.ledgerVersion); utils.getRecursive(getter, options.limit, composeAsync((orders) => _.sortBy(orders, (order) => order.properties.sequence), callback)); diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index 126dd353..e25a65a7 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -52,10 +52,10 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, validate.getTransactionOptions(options); const remote = this.remote; - const maxLedgerVersion = - options.maxLedgerVersion || remote.getLedgerSequence(); - function callbackWrapper(error_?: Error, tx?: Object) { + function callbackWrapper(error_?: Error, tx?: Object, + maxLedgerVersion?: number + ) { let error = error_; if (!error && tx && tx.validated !== true) { @@ -89,11 +89,22 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, } } + + /* eslint-disable no-unused-vars, handle-callback-err */ + function callbackWrapper2(error_?: Error, tx?: Object) { + remote.getLedgerSequence(function(err?, seq: number) { + const maxLedgerVersion = Math.min(options.maxLedgerVersion || Infinity, + seq); + callbackWrapper(error_, tx, maxLedgerVersion); + }); + } + /* eslint-enable no-unused-vars, handle-callback-err */ + async.waterfall([ _.partial(remote.requestTx.bind(remote), {hash: identifier, binary: false}), _.partial(attachTransactionDate, remote) - ], callbackWrapper); + ], callbackWrapper2); } function getTransaction(identifier: string, diff --git a/src/api/ledger/trustlines.js b/src/api/ledger/trustlines.js index eefd1fd8..74093c1a 100644 --- a/src/api/ledger/trustlines.js +++ b/src/api/ledger/trustlines.js @@ -42,10 +42,21 @@ function getTrustlinesAsync(account: string, options: {currency: string, validate.address(account); validate.getTrustlinesOptions(options); - const ledgerVersion = options.ledgerVersion - || this.remote.getLedgerSequence(); + if (!options.ledgerVersion) { + const self = this; + this.remote.getLedgerSequence(convertErrors(function(err?, seq: number) { + if (err) { + callback(err); + } else { + const newOptions = _.extend(options, {ledgerVersion: seq}); + getTrustlinesAsync.call(self, account, newOptions, callback); + } + })); + return; + } + const getter = _.partial(getAccountLines, this.remote, account, - ledgerVersion, options); + options.ledgerVersion, options); utils.getRecursive(getter, options.limit, callback); } diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index 75a966d2..dcb88ebd 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -103,7 +103,7 @@ function hasCompleteLedgerRange(remote: Remote, minLedgerVersion?: number, const firstLedgerVersion = 32570; // earlier versions have been lost return remote.getServer().hasLedgerRange( minLedgerVersion || firstLedgerVersion, - maxLedgerVersion || remote.getLedgerSequence()); + maxLedgerVersion || remote.getLedgerSequenceSync()); } function isPendingLedgerVersion(remote: Remote, maxLedgerVersion: ?number diff --git a/src/api/server/server.js b/src/api/server/server.js index 51417e27..9b2998ec 100644 --- a/src/api/server/server.js +++ b/src/api/server/server.js @@ -53,8 +53,7 @@ function getServerInfoAsync( ): void { this.remote.requestServerInfo((error, response) => { if (error) { - const message = - _.get(error, ['remote', 'error_message'], error.message); + const message = _.get(error, ['remote', 'error_message'], error.message); callback(new common.errors.RippledNetworkError(message)); } else { callback(null, @@ -63,28 +62,43 @@ function getServerInfoAsync( }); } -function getFee(): number { - return common.dropsToXrp(this.remote.createTransaction()._computeFee()); +function getFee(): ?number { + if (!this.remote._servers.length) { + throw new common.errors.RippledNetworkError('No servers available.'); + } + const fee = this.remote.createTransaction()._computeFee(); + if (typeof fee !== 'string') { + return undefined; + } + return common.dropsToXrp(fee); } -function getLedgerVersion(): number { - return this.remote.getLedgerSequence(); +function getLedgerVersion(): Promise { + return common.promisify(this.remote.getLedgerSequence).call(this.remote); } function connect(): Promise { return common.promisify(callback => { - this.remote.connect(() => callback(null)); + try { + this.remote.connect(() => callback(null)); + } catch(error) { + callback(new common.errors.RippledNetworkError(error.message)); + } })(); } function disconnect(): Promise { return common.promisify(callback => { - this.remote.disconnect(() => callback(null)); + try { + this.remote.disconnect(() => callback(null)); + } catch(error) { + callback(new common.errors.RippledNetworkError(error.message)); + } })(); } function getServerInfo(): Promise { - return common.promisify(getServerInfoAsync.bind(this))(); + return common.promisify(getServerInfoAsync).call(this); } function rippleTimeToISO8601(rippleTime: string): string { diff --git a/src/api/transaction/utils.js b/src/api/transaction/utils.js index a72b72fb..0512360d 100644 --- a/src/api/transaction/utils.js +++ b/src/api/transaction/utils.js @@ -1,8 +1,10 @@ /* @flow */ 'use strict'; const _ = require('lodash'); +const async = require('async'); const BigNumber = require('bignumber.js'); const common = require('../common'); +const composeAsync = common.composeAsync; function setTransactionBitFlags(transaction: any, values: any, flags: any ): void { @@ -19,9 +21,11 @@ function setTransactionBitFlags(transaction: any, values: any, flags: any } } -function getFeeDrops(remote) { +function getFeeDrops(remote, callback) { const feeUnits = 10; // all transactions currently have a fee of 10 fee units - return remote.feeTx(feeUnits).to_text(); + remote.feeTxAsync(feeUnits, (err, data) => { + callback(err, data ? data.to_text() : undefined); + }); } function formatPrepareResponse(txJSON) { @@ -39,42 +43,64 @@ function formatPrepareResponse(txJSON) { type Callback = (err: ?(typeof Error), data: {txJSON: string, instructions: any}) => void; function prepareTransaction(transaction: any, remote: any, instructions: any, - callback: Callback): void { + callback: Callback +): void { common.validate.instructions(instructions); transaction.complete(); const account = transaction.getAccount(); const txJSON = transaction.tx_json; - if (instructions.maxLedgerVersion !== undefined) { - txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10); - } else { - const offset = instructions.maxLedgerVersionOffset !== undefined ? - parseInt(instructions.maxLedgerVersionOffset, 10) : 3; - txJSON.LastLedgerSequence = remote.getLedgerSequence() + offset; - } - if (instructions.fee !== undefined) { - txJSON.Fee = common.xrpToDrops(instructions.fee); - } else { - const serverFeeDrops = getFeeDrops(remote); - if (instructions.maxFee !== undefined) { - const maxFeeDrops = common.xrpToDrops(instructions.maxFee); - txJSON.Fee = BigNumber.min(serverFeeDrops, maxFeeDrops).toString(); + function prepare1(callback_) { + if (instructions.maxLedgerVersion !== undefined) { + txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10); + callback_(); } else { - txJSON.Fee = serverFeeDrops; + const offset = instructions.maxLedgerVersionOffset !== undefined ? + parseInt(instructions.maxLedgerVersionOffset, 10) : 3; + remote.getLedgerSequence((error, seq) => { + txJSON.LastLedgerSequence = seq + offset; + callback_(error); + }); } } - if (instructions.sequence !== undefined) { - txJSON.Sequence = parseInt(instructions.sequence, 10); - callback(null, formatPrepareResponse(txJSON)); - } else { - remote.findAccount(account).getNextSequence(function(error, sequence) { - txJSON.Sequence = sequence; - callback(error, formatPrepareResponse(txJSON)); - }); + function prepare2(callback_) { + if (instructions.fee !== undefined) { + txJSON.Fee = common.xrpToDrops(instructions.fee); + callback_(); + } else { + getFeeDrops(remote, composeAsync((serverFeeDrops) => { + if (instructions.maxFee !== undefined) { + const maxFeeDrops = common.xrpToDrops(instructions.maxFee); + txJSON.Fee = BigNumber.min(serverFeeDrops, maxFeeDrops).toString(); + } else { + txJSON.Fee = serverFeeDrops; + } + }, callback_)); + } } + + function prepare3(callback_) { + if (instructions.sequence !== undefined) { + txJSON.Sequence = parseInt(instructions.sequence, 10); + callback_(null, formatPrepareResponse(txJSON)); + } else { + remote.findAccount(account).getNextSequence(function(error, sequence) { + txJSON.Sequence = sequence; + callback_(error, formatPrepareResponse(txJSON)); + }); + } + } + + async.series([ + prepare1, + prepare2, + prepare3 + ], common.convertErrors(function(error, results) { + callback(error, results && results[2]); + })); } module.exports = { diff --git a/src/core/remote.js b/src/core/remote.js index 638def41..5804c479 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -36,6 +36,8 @@ const utils = require('./utils'); const hashprefixes = require('./hashprefixes'); const log = require('./log').internal.sub('remote'); +export type GetLedgerSequenceCallback = (err?: ?Error, index?: number) => void; + /** * Interface to manage connections to rippled servers * @@ -518,7 +520,35 @@ Remote.prototype._handleMessage = function(message, server) { } }; -Remote.prototype.getLedgerSequence = function() { +/** + * + * @param {Function} [callback] + * @api public + */ + +Remote.prototype.getLedgerSequence = function(callback = function() {}) { + if (!this._servers.length) { + callback(new Error('No servers available.')); + return; + } + + if (_.isFinite(this._ledger_current_index)) { + // the "current" ledger is the one after the most recently closed ledger + callback(null, this._ledger_current_index - 1); + } else { + const self = this; + this.once('ledger_closed', function() { + callback(null, self._ledger_current_index - 1); + }); + } +}; + +/** + * + * @api private + */ + +Remote.prototype.getLedgerSequenceSync = function(): number { if (!this._ledger_current_index) { throw new Error('Ledger sequence has not yet been initialized'); } @@ -2307,6 +2337,32 @@ Remote.prototype.feeTx = function(units) { return server._feeTx(units); }; +/** + * Same as feeTx, but will wait to connect to server if currently + * disconnected. + * + * @param {Number} fee units + * @param {Function} callback + */ + +Remote.prototype.feeTxAsync = function(units, callback) { + if (!this._servers.length) { + callback(new Error('No servers available.')); + return; + } + + let server = this.getServer(); + + if (!server) { + this.once('connected', () => { + server = this.getServer(); + callback(null, server._feeTx(units)); + }); + } else { + callback(null, server._feeTx(units)); + } +}; + /** * Get the current recommended transaction fee unit. * diff --git a/src/core/rippleerror.js b/src/core/rippleerror.js index 08d647f1..45096d17 100644 --- a/src/core/rippleerror.js +++ b/src/core/rippleerror.js @@ -1,3 +1,5 @@ +'use strict'; + const util = require('util'); const _ = require('lodash'); @@ -18,18 +20,23 @@ function RippleError(code?: any, message?: string) { } } - this.engine_result = this.result = (this.result || this.engine_result || this.error || 'Error'); - this.engine_result_message = this.result_message = (this.result_message || this.engine_result_message || this.error_message || 'Error'); + this.engine_result = this.result = (this.result || this.engine_result || + this.error || 'Error'); + this.engine_result_message = this.result_message = (this.result_message || + this.engine_result_message || this.error_message || 'Error'); this.message = this.result_message; let stack; - if (!!Error.captureStackTrace) { + if (Boolean(Error.captureStackTrace)) { Error.captureStackTrace(this, code || this); - } else if ((stack = new Error().stack)) { - this.stack = stack; + } else { + stack = new Error().stack; + if (Boolean(stack)) { + this.stack = stack; + } } -}; +} util.inherits(RippleError, Error); diff --git a/test/api-test.js b/test/api-test.js index 9e2f4225..a050c4d3 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -52,13 +52,15 @@ describe('RippleAPI', function() { }); it('preparePayment with all options specified', function() { - const localInstructions = { - maxLedgerVersion: this.api.getLedgerVersion() + 100, - fee: '0.000012' - }; - return this.api.preparePayment( - address, requests.preparePaymentAllOptions, localInstructions).then( - _.partial(checkResult, responses.preparePaymentAllOptions, 'prepare')); + return this.api.getLedgerVersion().then((ver) => { + const localInstructions = { + maxLedgerVersion: ver + 100, + fee: '0.000012' + }; + return this.api.preparePayment( + address, requests.preparePaymentAllOptions, localInstructions).then( + _.partial(checkResult, responses.preparePaymentAllOptions, 'prepare')); + }); }); it('preparePayment without counterparty set', function() { @@ -637,8 +639,13 @@ describe('RippleAPI', function() { }); }); - it('getLedgerVersion', function() { - assert.strictEqual(this.api.getLedgerVersion(), 8819951); + it('getLedgerVersion', function(done) { + this.api.getLedgerVersion().then((ver) => { + assert.strictEqual(ver, 8819951); + done(); + }, (err) => { + done(err); + }); }); it('getLedger', function() { From d573c5746ba11acf591b87a0c82c6313ac8a99a3 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Sun, 20 Sep 2015 23:59:36 +0300 Subject: [PATCH 11/22] refactor getLedgerVersionHelper --- src/api/ledger/balances.js | 11 +++++------ src/api/ledger/orders.js | 18 ++++-------------- src/api/ledger/transaction.js | 10 ++++------ src/api/ledger/trustlines.js | 18 ++++-------------- src/api/ledger/utils.js | 18 +++++++++++++++++- src/api/server/server.js | 7 ++----- src/api/transaction/utils.js | 12 ++++++------ src/core/remote.js | 5 ++--- src/core/transaction.js | 2 +- src/core/transactionmanager.js | 2 +- test/api-test.js | 4 +--- test/transaction-manager-test.js | 2 +- 12 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/api/ledger/balances.js b/src/api/ledger/balances.js index fba119a5..856031bd 100644 --- a/src/api/ledger/balances.js +++ b/src/api/ledger/balances.js @@ -34,25 +34,24 @@ function getTrustlinesAsync(account, options, callback) { .catch(callback); } -function getLedgerVersion(remote: Remote, optionValue?: number, +function getLedgerVersionHelper(remote: Remote, optionValue?: number, callback: GetLedgerSequenceCallback ) { - if (typeof optionValue === 'number') { + if (optionValue !== undefined && optionValue !== null) { callback(null, optionValue); } else { remote.getLedgerSequence(callback); } } - function getBalancesAsync(account, options, callback) { validate.address(account); validate.getBalancesOptions(options); async.parallel({ - xrp: async.compose( - _.partial(utils.getXRPBalance, this.remote, account), - _.partial(getLedgerVersion, this.remote, options.ledgerVersion) + xrp: async.seq( + _.partial(getLedgerVersionHelper, this.remote, options.ledgerVersion), + _.partial(utils.getXRPBalance, this.remote, account) ), trustlines: _.partial(getTrustlinesAsync.bind(this), account, options) }, composeAsync(formatBalances, convertErrors(callback))); diff --git a/src/api/ledger/orders.js b/src/api/ledger/orders.js index cfb507ec..47e78577 100644 --- a/src/api/ledger/orders.js +++ b/src/api/ledger/orders.js @@ -1,6 +1,7 @@ /* @flow */ 'use strict'; const _ = require('lodash'); +const async = require('async'); const utils = require('./utils'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; @@ -26,19 +27,6 @@ function getOrdersAsync(account, options, callback) { validate.address(account); validate.getOrdersOptions(options); - if (!options.ledgerVersion) { - const self = this; - this.remote.getLedgerSequence((err, seq) => { - if (err) { - convertErrors(callback)(err); - return; - } - const newOptions = _.extend(options, {ledgerVersion: seq}); - getOrdersAsync.call(self, account, newOptions, callback); - }); - return; - } - const getter = _.partial(requestAccountOffers, this.remote, account, options.ledgerVersion); utils.getRecursive(getter, options.limit, @@ -47,7 +35,9 @@ function getOrdersAsync(account, options, callback) { } function getOrders(account: string, options = {}) { - return utils.promisify(getOrdersAsync).call(this, account, options); + return utils.promisify(async.seq( + utils.getLedgerOptionsWithLedgerVersion, + getOrdersAsync)).call(this, account, options); } module.exports = getOrders; diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index e25a65a7..6a2fe4de 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -90,21 +90,19 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, } - /* eslint-disable no-unused-vars, handle-callback-err */ - function callbackWrapper2(error_?: Error, tx?: Object) { + function maxLedgerGetter(error_?: Error, tx?: Object) { remote.getLedgerSequence(function(err?, seq: number) { - const maxLedgerVersion = Math.min(options.maxLedgerVersion || Infinity, - seq); + _.noop(err); + const maxLedgerVersion = options.maxLedgerVersion || seq; callbackWrapper(error_, tx, maxLedgerVersion); }); } - /* eslint-enable no-unused-vars, handle-callback-err */ async.waterfall([ _.partial(remote.requestTx.bind(remote), {hash: identifier, binary: false}), _.partial(attachTransactionDate, remote) - ], callbackWrapper2); + ], maxLedgerGetter); } function getTransaction(identifier: string, diff --git a/src/api/ledger/trustlines.js b/src/api/ledger/trustlines.js index 74093c1a..824de0c6 100644 --- a/src/api/ledger/trustlines.js +++ b/src/api/ledger/trustlines.js @@ -1,6 +1,7 @@ /* @flow */ 'use strict'; const _ = require('lodash'); +const async = require('async'); const utils = require('./utils'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; @@ -42,26 +43,15 @@ function getTrustlinesAsync(account: string, options: {currency: string, validate.address(account); validate.getTrustlinesOptions(options); - if (!options.ledgerVersion) { - const self = this; - this.remote.getLedgerSequence(convertErrors(function(err?, seq: number) { - if (err) { - callback(err); - } else { - const newOptions = _.extend(options, {ledgerVersion: seq}); - getTrustlinesAsync.call(self, account, newOptions, callback); - } - })); - return; - } - const getter = _.partial(getAccountLines, this.remote, account, options.ledgerVersion, options); utils.getRecursive(getter, options.limit, callback); } function getTrustlines(account: string, options = {}) { - return utils.promisify(getTrustlinesAsync).call(this, account, options); + return utils.promisify(async.seq( + utils.getLedgerOptionsWithLedgerVersion, + getTrustlinesAsync)).call(this, account, options); } module.exports = getTrustlines; diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index dcb88ebd..c264658b 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -108,12 +108,28 @@ function hasCompleteLedgerRange(remote: Remote, minLedgerVersion?: number, function isPendingLedgerVersion(remote: Remote, maxLedgerVersion: ?number ): boolean { - const currentLedger = remote.getLedgerSequence(); + const currentLedger = remote.getLedgerSequenceSync(); return currentLedger < (maxLedgerVersion || 0); } +function getLedgerOptionsWithLedgerVersion(account: string, options: Object, + callback: (err?: ?Error, account?: string, options: Object) => void +) { + if (Boolean(options) && options.ledgerVersion !== undefined && + options.ledgerVersion !== null + ) { + callback(null, account, options); + } else { + this.remote.getLedgerSequence(common.convertErrors((err, sequence) => { + callback(err, account, _.assign({}, options, { + ledgerVersion: sequence})); + })); + } +} + module.exports = { getXRPBalance, + getLedgerOptionsWithLedgerVersion, compareTransactions, renameCounterpartyToIssuer, renameCounterpartyToIssuerInOrder, diff --git a/src/api/server/server.js b/src/api/server/server.js index 9b2998ec..55b182b5 100644 --- a/src/api/server/server.js +++ b/src/api/server/server.js @@ -63,14 +63,11 @@ function getServerInfoAsync( } function getFee(): ?number { - if (!this.remote._servers.length) { + if (!this.remote.getConnectedServers().length) { throw new common.errors.RippledNetworkError('No servers available.'); } const fee = this.remote.createTransaction()._computeFee(); - if (typeof fee !== 'string') { - return undefined; - } - return common.dropsToXrp(fee); + return fee === undefined ? undefined : common.dropsToXrp(fee); } function getLedgerVersion(): Promise { diff --git a/src/api/transaction/utils.js b/src/api/transaction/utils.js index 0512360d..1bd08d63 100644 --- a/src/api/transaction/utils.js +++ b/src/api/transaction/utils.js @@ -52,7 +52,7 @@ function prepareTransaction(transaction: any, remote: any, instructions: any, const txJSON = transaction.tx_json; - function prepare1(callback_) { + function prepareMaxLedgerVersion(callback_) { if (instructions.maxLedgerVersion !== undefined) { txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10); callback_(); @@ -66,7 +66,7 @@ function prepareTransaction(transaction: any, remote: any, instructions: any, } } - function prepare2(callback_) { + function prepareFee(callback_) { if (instructions.fee !== undefined) { txJSON.Fee = common.xrpToDrops(instructions.fee); callback_(); @@ -82,7 +82,7 @@ function prepareTransaction(transaction: any, remote: any, instructions: any, } } - function prepare3(callback_) { + function prepareSequence(callback_) { if (instructions.sequence !== undefined) { txJSON.Sequence = parseInt(instructions.sequence, 10); callback_(null, formatPrepareResponse(txJSON)); @@ -95,9 +95,9 @@ function prepareTransaction(transaction: any, remote: any, instructions: any, } async.series([ - prepare1, - prepare2, - prepare3 + prepareMaxLedgerVersion, + prepareFee, + prepareSequence ], common.convertErrors(function(error, results) { callback(error, results && results[2]); })); diff --git a/src/core/remote.js b/src/core/remote.js index 5804c479..af4d73cf 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -536,9 +536,8 @@ Remote.prototype.getLedgerSequence = function(callback = function() {}) { // the "current" ledger is the one after the most recently closed ledger callback(null, this._ledger_current_index - 1); } else { - const self = this; - this.once('ledger_closed', function() { - callback(null, self._ledger_current_index - 1); + this.once('ledger_closed', () => { + callback(null, this._ledger_current_index - 1); }); } }; diff --git a/src/core/transaction.js b/src/core/transaction.js index 0a54b4cb..6cd0a7ab 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -581,7 +581,7 @@ Transaction.prototype.setLastLedgerSequence = function(sequence) { assert(this.remote, 'Unable to set LastLedgerSequence, missing Remote'); this._setUInt32('LastLedgerSequence', - this.remote.getLedgerSequence() + 1 + this.remote.getLedgerSequenceSync() + 1 + this.getLastLedgerSequenceOffset()); } diff --git a/src/core/transactionmanager.js b/src/core/transactionmanager.js index fb5c241d..872f61a1 100644 --- a/src/core/transactionmanager.js +++ b/src/core/transactionmanager.js @@ -681,7 +681,7 @@ TransactionManager.prototype._request = function(tx) { } } - tx.submitIndex = this._remote.getLedgerSequence() + 1; + tx.submitIndex = this._remote.getLedgerSequenceSync() + 1; if (tx.attempts === 0) { tx.initialSubmitIndex = tx.submitIndex; diff --git a/test/api-test.js b/test/api-test.js index a050c4d3..0be3f414 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -643,9 +643,7 @@ describe('RippleAPI', function() { this.api.getLedgerVersion().then((ver) => { assert.strictEqual(ver, 8819951); done(); - }, (err) => { - done(err); - }); + }, done); }); it('getLedger', function() { diff --git a/test/transaction-manager-test.js b/test/transaction-manager-test.js index 7a083eec..aecd04c2 100644 --- a/test/transaction-manager-test.js +++ b/test/transaction-manager-test.js @@ -696,7 +696,7 @@ describe('TransactionManager', function() { assert.strictEqual(summary.submissionAttempts, 0); assert.strictEqual(summary.submitIndex, undefined); assert.strictEqual(summary.initialSubmitIndex, undefined); - assert.strictEqual(summary.lastLedgerSequence, remote.getLedgerSequence() + 1 + Remote.DEFAULTS.last_ledger_offset); + assert.strictEqual(summary.lastLedgerSequence, remote.getLedgerSequenceSync() + 1 + Remote.DEFAULTS.last_ledger_offset); assert.strictEqual(summary.state, 'failed'); assert.strictEqual(summary.finalized, true); assert.deepEqual(summary.result, { From ac781710990a5dacd73018f4f39ce30e98fc6fd8 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Wed, 30 Sep 2015 07:46:30 +0300 Subject: [PATCH 12/22] make orderbook use Remote.getLedgerSequenceSync fix some variables naming in src/api --- src/api/ledger/transaction.js | 9 ++++----- src/api/ledger/utils.js | 7 +++---- src/api/transaction/utils.js | 4 ++-- src/core/orderbook.js | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index 6a2fe4de..8d13714e 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -91,18 +91,17 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, function maxLedgerGetter(error_?: Error, tx?: Object) { - remote.getLedgerSequence(function(err?, seq: number) { - _.noop(err); - const maxLedgerVersion = options.maxLedgerVersion || seq; + this.getLedgerVersion().then((version) => { + const maxLedgerVersion = options.maxLedgerVersion || version; callbackWrapper(error_, tx, maxLedgerVersion); - }); + }, callbackWrapper); } async.waterfall([ _.partial(remote.requestTx.bind(remote), {hash: identifier, binary: false}), _.partial(attachTransactionDate, remote) - ], maxLedgerGetter); + ], maxLedgerGetter.bind(this)); } function getTransaction(identifier: string, diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index c264658b..eb0fe5ee 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -120,10 +120,9 @@ function getLedgerOptionsWithLedgerVersion(account: string, options: Object, ) { callback(null, account, options); } else { - this.remote.getLedgerSequence(common.convertErrors((err, sequence) => { - callback(err, account, _.assign({}, options, { - ledgerVersion: sequence})); - })); + this.getLedgerVersion().then((version) => { + callback(null, account, _.assign({}, options, {ledgerVersion: version})); + }, callback); } } diff --git a/src/api/transaction/utils.js b/src/api/transaction/utils.js index 1bd08d63..08636d42 100644 --- a/src/api/transaction/utils.js +++ b/src/api/transaction/utils.js @@ -59,8 +59,8 @@ function prepareTransaction(transaction: any, remote: any, instructions: any, } else { const offset = instructions.maxLedgerVersionOffset !== undefined ? parseInt(instructions.maxLedgerVersionOffset, 10) : 3; - remote.getLedgerSequence((error, seq) => { - txJSON.LastLedgerSequence = seq + offset; + remote.getLedgerSequence((error, ledgerVersion) => { + txJSON.LastLedgerSequence = ledgerVersion + offset; callback_(error); }); } diff --git a/src/core/orderbook.js b/src/core/orderbook.js index 185f7ec0..b1a997d9 100644 --- a/src/core/orderbook.js +++ b/src/core/orderbook.js @@ -846,7 +846,7 @@ OrderBook.prototype.onTransaction = function(transaction) { if (--this._transactionsLeft === 0 && !this._waitingForOffers) { - const lastClosedLedger = this._remote.getLedgerSequence(); + const lastClosedLedger = this._remote.getLedgerSequenceSync(); if (this._isAutobridgeable) { if (this._canRunAutobridgeCalc()) { if (this._legOneBook._lastUpdateLedgerSequence === lastClosedLedger || @@ -1080,7 +1080,7 @@ OrderBook.prototype.notify = function(transaction) { this.emit('transaction', transaction); - this._lastUpdateLedgerSequence = this._remote.getLedgerSequence(); + this._lastUpdateLedgerSequence = this._remote.getLedgerSequenceSync(); if (!takerGetsTotal.is_zero()) { this.emit('trade', takerPaysTotal, takerGetsTotal); From 4676ade4eedff6cb79b87a6c4b43efd86fb1ee00 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Thu, 1 Oct 2015 11:45:12 -0700 Subject: [PATCH 13/22] Fix bug in Amount.ratio_human --- src/core/amount.js | 6 +----- test/amount-test.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/core/amount.js b/src/core/amount.js index f2959d4d..d207df51 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -120,18 +120,14 @@ Amount.from_components_unsafe = function(value: Value, currency: Currency, // be sure that _is_native is set properly BEFORE calling _set_value Amount.prototype._set_value = function(value: Value) { - this._value = value.isZero() && value.isNegative() ? value.negate() : value; this._check_limits(); - }; // Returns a new value which is the absolute value of this. Amount.prototype.abs = function() { - return this._copy(this._value.abs()); - }; Amount.prototype.add = function(addend) { @@ -229,7 +225,7 @@ Amount.prototype.ratio_human = function(denom, opts) { // // To compensate, we multiply the numerator by 10^xns_precision. if (denominator._is_native) { - numerator._set_value(numerator.multiply(bi_xns_unit)); + numerator._set_value(numerator._value.multiply(bi_xns_unit)); } return numerator.divide(denominator); diff --git a/test/amount-test.js b/test/amount-test.js index fa6c3fc4..49753b17 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -1048,6 +1048,17 @@ describe('Amount', function() { }); describe('ratio_human', function() { + it('Divide USD by XRP', function() { + const a = Amount.from_json({ + value: '0.08161672093323858', + currency: 'USD', + issuer: 'rLFPPebckMYZf3urdomLsaqRGmQ6zHVrrK' + }); + const b = Amount.from_json('15000000'); + const c = a.ratio_human(b); + assert.deepEqual(c.to_json(), {value: '0.005441114728882572', + currency: 'USD', issuer: 'rLFPPebckMYZf3urdomLsaqRGmQ6zHVrrK'}); + }); it('Divide USD by XAU (dem)', function() { assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); From 35acbb62c31ee70124e55b4df6ef784e2e62ff16 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Tue, 29 Sep 2015 11:03:46 -0700 Subject: [PATCH 14/22] Support source.amount in getPaths and destination.minAmount in preparePayment --- src/api/common/schemas/adjustment.json | 16 +- .../common/schemas/destinationAdjustment.json | 9 + src/api/common/schemas/get-paths.json | 4 +- src/api/common/schemas/lax-amount.json | 13 + src/api/common/schemas/lax-lax-amount.json | 13 + src/api/common/schemas/max-adjustment.json | 16 +- src/api/common/schemas/min-adjustment.json | 12 + src/api/common/schemas/pathfind.json | 14 +- src/api/common/schemas/payment.json | 4 +- src/api/common/schemas/sourceAdjustment.json | 9 + src/api/common/schemas/tag.json | 6 + src/api/ledger/parse/pathfind.js | 52 ++- src/api/ledger/pathfind.js | 27 +- src/api/transaction/payment.js | 58 +++- src/core/pathfind.js | 7 +- src/core/remote.js | 6 +- test/api-test.js | 17 +- .../api/requests/getpaths/send-all.json | 15 + test/fixtures/api/requests/index.js | 3 +- .../requests/prepare-payment-all-options.json | 1 - .../api/responses/get-paths-send-all.json | 70 ++++ .../api/responses/get-paths-send-usd.json | 3 +- test/fixtures/api/responses/get-paths.json | 6 +- test/fixtures/api/responses/index.js | 13 +- .../prepare-payment-all-options.json | 2 +- .../responses/prepare-payment-min-amount.json | 8 + test/fixtures/api/rippled/index.js | 1 + .../api/rippled/path-find-send-all.json | 313 ++++++++++++++++++ test/mock-rippled.js | 14 +- 29 files changed, 641 insertions(+), 91 deletions(-) create mode 100644 src/api/common/schemas/destinationAdjustment.json create mode 100644 src/api/common/schemas/lax-amount.json create mode 100644 src/api/common/schemas/lax-lax-amount.json create mode 100644 src/api/common/schemas/min-adjustment.json create mode 100644 src/api/common/schemas/sourceAdjustment.json create mode 100644 src/api/common/schemas/tag.json create mode 100644 test/fixtures/api/requests/getpaths/send-all.json create mode 100644 test/fixtures/api/responses/get-paths-send-all.json create mode 100644 test/fixtures/api/responses/prepare-payment-min-amount.json create mode 100644 test/fixtures/api/rippled/path-find-send-all.json diff --git a/src/api/common/schemas/adjustment.json b/src/api/common/schemas/adjustment.json index 901c78a8..d65812f5 100644 --- a/src/api/common/schemas/adjustment.json +++ b/src/api/common/schemas/adjustment.json @@ -4,20 +4,8 @@ "type": "object", "properties": { "address": {"$ref": "address"}, - "amount": { - "type": "object", - "properties": { - "currency": {"$ref": "currency"}, - "counterparty": {"$ref": "address"}, - "value": {"$ref": "value"} - }, - "required": ["currency", "value"], - "additionalProperties": false - }, - "tag": { - "description": "A string representing an unsigned 32-bit integer most commonly used to refer to a sender's hosted account at a Ripple gateway", - "$ref": "uint32" - } + "amount": {"$ref": "amount"}, + "tag": {"$ref": "tag"} }, "required": ["address", "amount"], "additionalProperties": false diff --git a/src/api/common/schemas/destinationAdjustment.json b/src/api/common/schemas/destinationAdjustment.json new file mode 100644 index 00000000..65f02d3e --- /dev/null +++ b/src/api/common/schemas/destinationAdjustment.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "destinationAdjustment", + "type": "object", + "oneOf": [ + {"$ref": "adjustment"}, + {"$ref": "minAdjustment"} + ] +} diff --git a/src/api/common/schemas/get-paths.json b/src/api/common/schemas/get-paths.json index db658c55..199dc8a0 100644 --- a/src/api/common/schemas/get-paths.json +++ b/src/api/common/schemas/get-paths.json @@ -5,8 +5,8 @@ "items": { "type": "object", "properties": { - "source": {"$ref": "maxAdjustment"}, - "destination": {"$ref": "adjustment"}, + "source": {"$ref": "sourceAdjustment"}, + "destination": {"$ref": "destinationAdjustment"}, "paths": {"type": "string"} }, "required": ["source", "destination", "paths"], diff --git a/src/api/common/schemas/lax-amount.json b/src/api/common/schemas/lax-amount.json new file mode 100644 index 00000000..51a3eeef --- /dev/null +++ b/src/api/common/schemas/lax-amount.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "laxAmount", + "description": "Amount where counterparty is optional", + "type": "object", + "properties": { + "currency": {"$ref": "currency"}, + "counterparty": {"$ref": "address"}, + "value": {"$ref": "value"} + }, + "required": ["currency", "value"], + "additionalProperties": false +} diff --git a/src/api/common/schemas/lax-lax-amount.json b/src/api/common/schemas/lax-lax-amount.json new file mode 100644 index 00000000..0b27a766 --- /dev/null +++ b/src/api/common/schemas/lax-lax-amount.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "laxLaxAmount", + "description": "Amount where counterparty and value are optional", + "type": "object", + "properties": { + "currency": {"$ref": "currency"}, + "counterparty": {"$ref": "address"}, + "value": {"$ref": "value"} + }, + "required": ["currency"], + "additionalProperties": false +} diff --git a/src/api/common/schemas/max-adjustment.json b/src/api/common/schemas/max-adjustment.json index b4a9eb1e..e63adb53 100644 --- a/src/api/common/schemas/max-adjustment.json +++ b/src/api/common/schemas/max-adjustment.json @@ -4,20 +4,8 @@ "type": "object", "properties": { "address": {"$ref": "address"}, - "maxAmount": { - "type": "object", - "properties": { - "currency": {"$ref": "currency"}, - "counterparty": {"$ref": "address"}, - "value": {"$ref": "value"} - }, - "required": ["currency", "value"], - "additionalProperties": false - }, - "tag": { - "description": "A string representing an unsigned 32-bit integer most commonly used to refer to a sender's hosted account at a Ripple gateway", - "$ref": "uint32" - } + "maxAmount": {"$ref": "laxAmount"}, + "tag": {"$ref": "tag"} }, "required": ["address", "maxAmount"], "additionalProperties": false diff --git a/src/api/common/schemas/min-adjustment.json b/src/api/common/schemas/min-adjustment.json new file mode 100644 index 00000000..ef34447c --- /dev/null +++ b/src/api/common/schemas/min-adjustment.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "minAdjustment", + "type": "object", + "properties": { + "address": {"$ref": "address"}, + "minAmount": {"$ref": "laxAmount"}, + "tag": {"$ref": "tag"} + }, + "required": ["address", "minAmount"], + "additionalProperties": false +} diff --git a/src/api/common/schemas/pathfind.json b/src/api/common/schemas/pathfind.json index 2302f2c7..4a37f6e2 100644 --- a/src/api/common/schemas/pathfind.json +++ b/src/api/common/schemas/pathfind.json @@ -7,6 +7,7 @@ "type": "object", "properties": { "address": {"$ref": "address"}, + "amount": {"$ref": "laxAmount"}, "currencies": { "type": "array", "items": { @@ -19,12 +20,23 @@ "additionalProperties": false }, "uniqueItems": true + }, + "not": { + "required": ["amount", "currencies"] } }, "additionalProperties": false, "required": ["address"] }, - "destination": {"$ref": "adjustment"} + "destination": { + "type": "object", + "properties": { + "address": {"$ref": "address"}, + "amount": {"$ref": "laxLaxAmount"} + }, + "required": ["address", "amount"], + "additionalProperties": false + } }, "required": ["source", "destination"], "additionalProperties": false diff --git a/src/api/common/schemas/payment.json b/src/api/common/schemas/payment.json index fa7b3842..40ec6480 100644 --- a/src/api/common/schemas/payment.json +++ b/src/api/common/schemas/payment.json @@ -3,8 +3,8 @@ "title": "payment", "type": "object", "properties": { - "source": {"$ref": "maxAdjustment"}, - "destination": {"$ref": "adjustment"}, + "source": {"$ref": "sourceAdjustment"}, + "destination": {"$ref": "destinationAdjustment"}, "paths": {"type": "string"}, "memos": { "type": "array", diff --git a/src/api/common/schemas/sourceAdjustment.json b/src/api/common/schemas/sourceAdjustment.json new file mode 100644 index 00000000..77c4806c --- /dev/null +++ b/src/api/common/schemas/sourceAdjustment.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "sourceAdjustment", + "type": "object", + "oneOf": [ + {"$ref": "adjustment"}, + {"$ref": "maxAdjustment"} + ] +} diff --git a/src/api/common/schemas/tag.json b/src/api/common/schemas/tag.json new file mode 100644 index 00000000..55028889 --- /dev/null +++ b/src/api/common/schemas/tag.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "tag", + "description": "A string representing an unsigned 32-bit integer most commonly used to refer to a sender's hosted account at a Ripple gateway", + "$ref": "uint32" +} diff --git a/src/api/ledger/parse/pathfind.js b/src/api/ledger/parse/pathfind.js index 39456d3c..0e996e8e 100644 --- a/src/api/ledger/parse/pathfind.js +++ b/src/api/ledger/parse/pathfind.js @@ -8,22 +8,42 @@ function parsePaths(paths) { _.omit(step, ['type', 'type_hex']))); } -function parsePathfind(sourceAddress: string, - destinationAmount: Object, pathfindResult: Object -): Object { - return pathfindResult.alternatives.map(function(alternative) { - return { - source: { - address: sourceAddress, - maxAmount: parseAmount(alternative.source_amount) - }, - destination: { - address: pathfindResult.destination_account, - amount: destinationAmount - }, - paths: JSON.stringify(parsePaths(alternative.paths_computed)) - }; - }); +function removeAnyCounterpartyEncoding(address: string, amount: Object) { + return amount.counterparty === address ? + _.omit(amount, 'counterparty') : amount; +} + +function createAdjustment(address: string, adjustmentWithoutAddress: Object) { + const amountKey = _.keys(adjustmentWithoutAddress)[0]; + const amount = adjustmentWithoutAddress[amountKey]; + return _.set({address: address}, amountKey, + removeAnyCounterpartyEncoding(address, amount)); +} + +function parseAlternative(sourceAddress: string, destinationAddress: string, + destinationAmount: Object, alternative: Object +) { + // we use "maxAmount"/"minAmount" here so that the result can be passed + // directly to preparePayment + const amounts = (alternative.destination_amount !== undefined) ? + {source: {amount: parseAmount(alternative.source_amount)}, + destination: {minAmount: parseAmount(alternative.destination_amount)}} : + {source: {maxAmount: parseAmount(alternative.source_amount)}, + destination: {amount: parseAmount(destinationAmount)}}; + + return { + source: createAdjustment(sourceAddress, amounts.source), + destination: createAdjustment(destinationAddress, amounts.destination), + paths: JSON.stringify(parsePaths(alternative.paths_computed)) + }; +} + +function parsePathfind(pathfindResult: Object): Object { + const sourceAddress = pathfindResult.source_account; + const destinationAddress = pathfindResult.destination_account; + const destinationAmount = pathfindResult.destination_amount; + return pathfindResult.alternatives.map(_.partial(parseAlternative, + sourceAddress, destinationAddress, destinationAmount)); } module.exports = parsePathfind; diff --git a/src/api/ledger/pathfind.js b/src/api/ledger/pathfind.js index 0af7f769..29a1e793 100644 --- a/src/api/ledger/pathfind.js +++ b/src/api/ledger/pathfind.js @@ -4,15 +4,18 @@ const _ = require('lodash'); const async = require('async'); const BigNumber = require('bignumber.js'); const utils = require('./utils'); -const validate = utils.common.validate; const parsePathfind = require('./parse/pathfind'); +const validate = utils.common.validate; const NotFoundError = utils.common.errors.NotFoundError; +const ValidationError = utils.common.errors.ValidationError; const composeAsync = utils.common.composeAsync; const convertErrors = utils.common.convertErrors; +const toRippledAmount = utils.common.toRippledAmount; type PathFindParams = { - src_currencies?: Array, src_account: string, dst_amount: string, - dst_account?: string + src_currencies?: Array, src_account: string, + dst_amount: string | Object, dst_account?: string, + src_amount?: string | Object } function addParams(params: PathFindParams, result: {}) { @@ -29,10 +32,11 @@ type PathFind = { } function requestPathFind(remote, pathfind: PathFind, callback) { + const destinationAmount = _.assign({value: -1}, pathfind.destination.amount); const params: PathFindParams = { src_account: pathfind.source.address, dst_account: pathfind.destination.address, - dst_amount: utils.common.toRippledAmount(pathfind.destination.amount) + dst_amount: toRippledAmount(destinationAmount) }; if (typeof params.dst_amount === 'object' && !params.dst_amount.issuer) { // Convert blank issuer to sender's address @@ -44,7 +48,17 @@ function requestPathFind(remote, pathfind: PathFind, callback) { } if (pathfind.source.currencies && pathfind.source.currencies.length > 0) { params.src_currencies = pathfind.source.currencies.map(amount => - _.omit(utils.common.toRippledAmount(amount), 'value')); + _.omit(toRippledAmount(amount), 'value')); + } + if (pathfind.source.amount) { + if (pathfind.destination.amount.value !== undefined) { + throw new ValidationError('Cannot specify both source.amount' + + ' and destination.amount.value in getPaths'); + } + params.src_amount = toRippledAmount(pathfind.source.amount); + if (params.src_amount.currency && !params.src_amount.issuer) { + params.src_amount.issuer = pathfind.source.address; + } } remote.createPathFind(params, @@ -81,8 +95,7 @@ function conditionallyAddDirectXRPPath(remote, address, paths, callback) { function formatResponse(pathfind, paths) { if (paths.alternatives && paths.alternatives.length > 0) { - const address = pathfind.source.address; - return parsePathfind(address, pathfind.destination.amount, paths); + return parsePathfind(paths); } if (paths.destination_currencies !== undefined && !_.includes(paths.destination_currencies, diff --git a/src/api/transaction/payment.js b/src/api/transaction/payment.js index 123f09ed..6103d907 100644 --- a/src/api/transaction/payment.js +++ b/src/api/transaction/payment.js @@ -5,6 +5,7 @@ const utils = require('./utils'); const validate = utils.common.validate; const toRippledAmount = utils.common.toRippledAmount; const Transaction = utils.common.core.Transaction; +const ValidationError = utils.common.errors.ValidationError; function isXRPToXRPPayment(payment) { const sourceCurrency = _.get(payment, 'source.maxAmount.currency'); @@ -23,24 +24,49 @@ function applyAnyCounterpartyEncoding(payment) { // https://ripple.com/build/transactions/ // #special-issuer-values-for-sendmax-and-amount // https://ripple.com/build/ripple-rest/#counterparties-in-payments - if (isIOUWithoutCounterparty(payment.source.maxAmount)) { - payment.source.maxAmount.counterparty = payment.source.address; - } - if (isIOUWithoutCounterparty(payment.destination.amount)) { - payment.destination.amount.counterparty = payment.destination.address; - } + _.forEach([payment.source, payment.destination], (adjustment) => { + _.forEach(['amount', 'minAmount', 'maxAmount'], (key) => { + if (isIOUWithoutCounterparty(adjustment[key])) { + adjustment[key].counterparty = adjustment.address; + } + }); + }); } -function createPaymentTransaction(account, payment) { +function createMaximalAmount(amount) { + const maxXRPValue = '100000000000'; + const maxIOUValue = '9999999999999999e80'; + const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue; + return _.assign(amount, {value: maxValue}); +} + +function createPaymentTransaction(account, paymentArgument) { + const payment = _.cloneDeep(paymentArgument); applyAnyCounterpartyEncoding(payment); validate.address(account); validate.payment(payment); + if ((payment.source.maxAmount && payment.destination.minAmount) || + (payment.source.amount && payment.destination.amount)) { + throw new ValidationError('payment must specify either (source.maxAmount ' + + 'and destination.amount) or (source.amount and destination.minAmount)'); + } + + // when using destination.minAmount, rippled still requires that we set + // a destination amount in addition to DeliverMin. the destination amount + // is interpreted as the maximum amount to send. we want to be sure to + // send the whole source amount, so we set the destination amount to the + // maximum possible amount. otherwise it's possible that the destination + // cap could be hit before the source cap. + const amount = payment.destination.minAmount && !isXRPToXRPPayment(payment) ? + createMaximalAmount(payment.destination.minAmount) : + (payment.destination.amount || payment.destination.minAmount); + const transaction = new Transaction(); transaction.payment({ from: payment.source.address, to: payment.destination.address, - amount: toRippledAmount(payment.destination.amount) + amount: toRippledAmount(amount) }); if (payment.invoiceID) { @@ -57,9 +83,6 @@ function createPaymentTransaction(account, payment) { transaction.addMemo(memo.type, memo.format, memo.data) ); } - if (payment.allowPartialPayment) { - transaction.setFlags(['PartialPayment']); - } if (payment.noDirectRipple) { transaction.setFlags(['NoRippleDirect']); } @@ -71,11 +94,22 @@ function createPaymentTransaction(account, payment) { // temREDUNDANT_SEND_MAX removed in: // https://github.com/ripple/rippled/commit/ // c522ffa6db2648f1d8a987843e7feabf1a0b7de8/ - transaction.sendMax(toRippledAmount(payment.source.maxAmount)); + if (payment.allowPartialPayment || payment.destination.minAmount) { + transaction.setFlags(['PartialPayment']); + } + + transaction.setSendMax(toRippledAmount( + payment.source.maxAmount || payment.source.amount)); + + if (payment.destination.minAmount) { + transaction.setDeliverMin(toRippledAmount(payment.destination.minAmount)); + } if (payment.paths) { transaction.paths(JSON.parse(payment.paths)); } + } else if (payment.allowPartialPayment) { + throw new ValidationError('XRP to XRP payments cannot be partial payments'); } return transaction; diff --git a/src/core/pathfind.js b/src/core/pathfind.js index 8a3359c7..1d646f51 100644 --- a/src/core/pathfind.js +++ b/src/core/pathfind.js @@ -11,7 +11,8 @@ const Amount = require('./amount').Amount; * the 'end' and 'superceded' events. */ -function PathFind(remote, src_account, dst_account, dst_amount, src_currencies +function PathFind(remote, src_account, dst_account, dst_amount, + src_currencies, src_amount ) { EventEmitter.call(this); @@ -21,6 +22,7 @@ function PathFind(remote, src_account, dst_account, dst_amount, src_currencies this.dst_account = dst_account; this.dst_amount = dst_amount; this.src_currencies = src_currencies; + this.src_amount = src_amount; } util.inherits(PathFind, EventEmitter); @@ -42,7 +44,8 @@ PathFind.prototype.create = function() { source_account: this.src_account, destination_account: this.dst_account, destination_amount: this.dst_amount, - source_currencies: this.src_currencies + source_currencies: this.src_currencies, + send_max: this.src_amount }); req.once('error', function(err) { diff --git a/src/core/remote.js b/src/core/remote.js index af4d73cf..7268c7ad 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -1824,7 +1824,7 @@ Remote.prototype.createPathFind = function(options, callback) { const pathFind = new PathFind(this, options.src_account, options.dst_account, - options.dst_amount, options.src_currencies); + options.dst_amount, options.src_currencies, options.src_amount); if (this._cur_path_find) { this._cur_path_find.notify_superceded(); @@ -2172,6 +2172,10 @@ Remote.prototype.requestPathFindCreate = function(options, callback) { options.source_currencies.map(Remote.prepareCurrency); } + if (options.send_max) { + request.message.send_max = Amount.json_rewrite(options.send_max); + } + request.callback(callback); return request; }; diff --git a/test/api-test.js b/test/api-test.js index 0be3f414..16a8fc6a 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -48,7 +48,7 @@ describe('RippleAPI', function() { }, instructions); return this.api.preparePayment( address, requests.preparePayment, localInstructions).then( - _.partial(checkResult, responses.preparePayment, 'prepare')); + _.partial(checkResult, responses.preparePayment.normal, 'prepare')); }); it('preparePayment with all options specified', function() { @@ -59,7 +59,7 @@ describe('RippleAPI', function() { }; return this.api.preparePayment( address, requests.preparePaymentAllOptions, localInstructions).then( - _.partial(checkResult, responses.preparePaymentAllOptions, 'prepare')); + _.partial(checkResult, responses.preparePayment.allOptions, 'prepare')); }); }); @@ -67,10 +67,16 @@ describe('RippleAPI', function() { const localInstructions = _.defaults({sequence: 23}, instructions); return this.api.preparePayment( address, requests.preparePaymentNoCounterparty, localInstructions).then( - _.partial(checkResult, responses.preparePaymentNoCounterparty, + _.partial(checkResult, responses.preparePayment.noCounterparty, 'prepare')); }); + it('preparePayment - destination.minAmount', function() { + return this.api.preparePayment(address, responses.getPaths.sendAll[0], + instructions).then(_.partial(checkResult, + responses.preparePayment.minAmount, 'prepare')); + }); + it('prepareOrder - buy order', function() { return this.api.prepareOrder(address, requests.prepareOrder, instructions) .then(_.partial(checkResult, responses.prepareOrder, 'prepare')); @@ -639,6 +645,11 @@ describe('RippleAPI', function() { }); }); + it('getPaths - send all', function() { + return this.api.getPaths(requests.getPaths.sendAll).then( + _.partial(checkResult, responses.getPaths.sendAll, 'getPaths')); + }); + it('getLedgerVersion', function(done) { this.api.getLedgerVersion().then((ver) => { assert.strictEqual(ver, 8819951); diff --git a/test/fixtures/api/requests/getpaths/send-all.json b/test/fixtures/api/requests/getpaths/send-all.json new file mode 100644 index 00000000..e0bd51f9 --- /dev/null +++ b/test/fixtures/api/requests/getpaths/send-all.json @@ -0,0 +1,15 @@ +{ + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "5" + } + }, + "destination": { + "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "amount": { + "currency": "USD" + } + } +} diff --git a/test/fixtures/api/requests/index.js b/test/fixtures/api/requests/index.js index 6ef8dc86..5660b449 100644 --- a/test/fixtures/api/requests/index.js +++ b/test/fixtures/api/requests/index.js @@ -25,7 +25,8 @@ module.exports = { XrpToXrpNotEnough: require('./getpaths/xrp2xrp-not-enough'), NotAcceptCurrency: require('./getpaths/not-accept-currency'), NoPaths: require('./getpaths/no-paths'), - NoPathsWithCurrencies: require('./getpaths/no-paths-with-currencies') + NoPathsWithCurrencies: require('./getpaths/no-paths-with-currencies'), + sendAll: require('./getpaths/send-all') }, computeLedgerHash: { header: require('./compute-ledger-hash'), diff --git a/test/fixtures/api/requests/prepare-payment-all-options.json b/test/fixtures/api/requests/prepare-payment-all-options.json index 4cc232fd..007c7f3b 100644 --- a/test/fixtures/api/requests/prepare-payment-all-options.json +++ b/test/fixtures/api/requests/prepare-payment-all-options.json @@ -23,7 +23,6 @@ } ], "invoiceID": "A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A", - "allowPartialPayment": true, "noDirectRipple": true, "limitQuality": true, "paths": "[[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"type\":49,\"type_hex\":\"0000000000000031\"},{\"currency\":\"LTC\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]" diff --git a/test/fixtures/api/responses/get-paths-send-all.json b/test/fixtures/api/responses/get-paths-send-all.json new file mode 100644 index 00000000..19a869c6 --- /dev/null +++ b/test/fixtures/api/responses/get-paths-send-all.json @@ -0,0 +1,70 @@ +[ + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "5" + } + }, + "destination": { + "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "minAmount": { + "currency": "USD", + "value": "4.93463759481038" + } + }, + "paths": "[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]]" + }, + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "5" + } + }, + "destination": { + "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "minAmount": { + "currency": "USD", + "value": "4.93463759481038" + } + }, + "paths": "[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"EUR\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}],[{\"account\":\"rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun\"},{\"currency\":\"USD\",\"issuer\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"}]]" + }, + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "5" + } + }, + "destination": { + "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "minAmount": { + "currency": "USD", + "value": "4.93463759481038" + } + }, + "paths": "[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"JPY\",\"issuer\":\"rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6\"},{\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]]" + }, + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "5" + } + }, + "destination": { + "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "minAmount": { + "currency": "USD", + "value": "4.990019960079841" + } + }, + "paths": "[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]]" + } +] diff --git a/test/fixtures/api/responses/get-paths-send-usd.json b/test/fixtures/api/responses/get-paths-send-usd.json index d2ba1250..f4d9c50d 100644 --- a/test/fixtures/api/responses/get-paths-send-usd.json +++ b/test/fixtures/api/responses/get-paths-send-usd.json @@ -4,8 +4,7 @@ "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", "maxAmount": { "currency": "USD", - "value": "0.000001002", - "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo" + "value": "0.000001002" } }, "destination": { diff --git a/test/fixtures/api/responses/get-paths.json b/test/fixtures/api/responses/get-paths.json index 00259882..610bb149 100644 --- a/test/fixtures/api/responses/get-paths.json +++ b/test/fixtures/api/responses/get-paths.json @@ -4,8 +4,7 @@ "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", "maxAmount": { "currency": "JPY", - "value": "0.1117218827811721", - "counterparty": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" + "value": "0.1117218827811721" } }, "destination": { @@ -23,8 +22,7 @@ "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", "maxAmount": { "currency": "USD", - "value": "0.001002", - "counterparty": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" + "value": "0.001002" } }, "destination": { diff --git a/test/fixtures/api/responses/index.js b/test/fixtures/api/responses/index.js index 064826d3..5080660a 100644 --- a/test/fixtures/api/responses/index.js +++ b/test/fixtures/api/responses/index.js @@ -9,7 +9,8 @@ module.exports = { getPaths: { XrpToUsd: require('./get-paths.json'), UsdToUsd: require('./get-paths-send-usd.json'), - XrpToXrp: require('./get-paths-xrp-to-xrp.json') + XrpToXrp: require('./get-paths-xrp-to-xrp.json'), + sendAll: require('./get-paths-send-all.json') }, getServerInfo: require('./get-server-info.json'), getSettings: require('./get-settings.json'), @@ -35,10 +36,12 @@ module.exports = { prepareOrderCancellation: require('./prepare-order-cancellation.json'), prepareOrder: require('./prepare-order.json'), prepareOrderSell: require('./prepare-order-sell.json'), - preparePayment: require('./prepare-payment.json'), - preparePaymentAllOptions: require('./prepare-payment-all-options.json'), - preparePaymentNoCounterparty: - require('./prepare-payment-no-counterparty.json'), + preparePayment: { + normal: require('./prepare-payment.json'), + allOptions: require('./prepare-payment-all-options.json'), + noCounterparty: require('./prepare-payment-no-counterparty.json'), + minAmount: require('./prepare-payment-min-amount.json') + }, prepareSettings: { regularKey: require('./prepare-settings-regular-key.json'), flags: require('./prepare-settings.json'), diff --git a/test/fixtures/api/responses/prepare-payment-all-options.json b/test/fixtures/api/responses/prepare-payment-all-options.json index 8fa93a15..abd6f768 100644 --- a/test/fixtures/api/responses/prepare-payment-all-options.json +++ b/test/fixtures/api/responses/prepare-payment-all-options.json @@ -1,5 +1,5 @@ { - "txJSON": "{\"Flags\":458752,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":\"10000\",\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\",\"MemoData\":\"7465787465642064617461\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "txJSON": "{\"Flags\":327680,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":\"10000\",\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\",\"MemoData\":\"7465787465642064617461\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", "instructions": { "fee": "12", "sequence": 23, diff --git a/test/fixtures/api/responses/prepare-payment-min-amount.json b/test/fixtures/api/responses/prepare-payment-min-amount.json new file mode 100644 index 00000000..e0fc2000 --- /dev/null +++ b/test/fixtures/api/responses/prepare-payment-min-amount.json @@ -0,0 +1,8 @@ +{ + "txJSON": "{\"Flags\":131072,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\",\"Amount\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"SendMax\":{\"value\":\"5\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"DeliverMin\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"Paths\":[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\",\"currency\":\"USD\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "instructions": { + "fee": "12", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index fc7d44b4..fdac6f4e 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -22,6 +22,7 @@ module.exports = { path_find: { generate: require('./path-find'), sendUSD: require('./path-find-send-usd'), + sendAll: require('./path-find-send-all'), XrpToXrp: require('./path-find-xrp-to-xrp'), srcActNotFound: require('./path-find-srcActNotFound') }, diff --git a/test/fixtures/api/rippled/path-find-send-all.json b/test/fixtures/api/rippled/path-find-send-all.json new file mode 100644 index 00000000..1e7317cd --- /dev/null +++ b/test/fixtures/api/rippled/path-find-send-all.json @@ -0,0 +1,313 @@ +{ + "alternatives": [ + { + "destination_amount": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "4.93463759481038" + }, + "paths_computed": [ + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + }, + { + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + }, + { + "currency": "USD", + "issuer": "rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + } + ] + ], + "source_amount": { + "currency": "USD", + "issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "value": "5" + } + }, + { + "destination_amount": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "4.93463759481038" + }, + "paths_computed": [ + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + }, + { + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "EUR", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + } + ] + ], + "source_amount": { + "currency": "USD", + "issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "value": "5" + } + }, + { + "destination_amount": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "4.93463759481038" + }, + "paths_computed": [ + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + }, + { + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "XRP", + "type": 16, + "type_hex": "0000000000000010" + }, + { + "currency": "USD", + "issuer": "rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "JPY", + "issuer": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + } + ] + ], + "source_amount": { + "currency": "USD", + "issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "value": "5" + } + }, + { + "destination_amount": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "4.990019960079841" + }, + "paths_computed": [ + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "type": 1, + "type_hex": "0000000000000001" + } + ], + [ + { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "type": 1, + "type_hex": "0000000000000001" + }, + { + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 48, + "type_hex": "0000000000000030" + }, + { + "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", + "type": 1, + "type_hex": "0000000000000001" + } + ] + ], + "source_amount": { + "currency": "USD", + "issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "value": "5" + } + } + ], + "destination_account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "destination_amount": { + "currency": "USD", + "issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "value": "-1" + }, + "full_reply": true, + "id": 1, + "source_account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "type": "path_find" +} diff --git a/test/mock-rippled.js b/test/mock-rippled.js index e76f94ae..3db74eb6 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -266,10 +266,18 @@ module.exports = function(port) { destination_amount: request.destination_amount, destination_address: request.destination_address }); + } else if (request.source_account === addresses.ACCOUNT) { + if (request.destination_account === + 'ra5nK24KXen9AHvsdFTKHSANinZseWnPcX') { + response = createResponse(request, fixtures.path_find.sendAll); + } else { + response = fixtures.path_find.generate.generateIOUPaymentPaths( + request.id, request.source_account, request.destination_account, + request.destination_amount); + } } else { - response = fixtures.path_find.generate.generateIOUPaymentPaths( - request.id, request.source_account, request.destination_account, - request.destination_amount); + assert(false, 'Unrecognized path find request: ' + + JSON.stringify(request)); } // delay response to simulate calculation time so we can test queuing setTimeout(() => conn.send(response), 20); From 323e402e0cff66d5453ec7571ca2203759d34d38 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Tue, 22 Sep 2015 06:55:51 -0700 Subject: [PATCH 15/22] Add requestGatewayBalances() to core --- src/core/remote.js | 23 +++++++++++++++++++++++ test/remote-test.js | 16 ++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/core/remote.js b/src/core/remote.js index 7268c7ad..fff2fb92 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -2280,6 +2280,29 @@ Remote.prototype.requestConnect = function(ip, port, callback) { return request; }; +Remote.prototype.requestGatewayBalances = function(options, callback) { + assert(_.isObject(options), 'Options missing'); + assert(options.account, 'Account missing'); + + const request = new Request(this, 'gateway_balances'); + + request.message.account = UInt160.json_rewrite(options.account); + + if (!_.isUndefined(options.hotwallet)) { + request.message.hotwallet = options.hotwallet; + } + if (!_.isUndefined(options.strict)) { + request.message.strict = options.strict; + } + if (!_.isUndefined(options.ledger)) { + request.selectLedger(options.ledger); + } + + request.callback(callback); + + return request; +}; + /** * Create a Transaction * diff --git a/test/remote-test.js b/test/remote-test.js index f44c91e2..81ae697c 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -1966,6 +1966,22 @@ describe('Remote', function() { }); }); + it.only('Construct gateway_balances request', function() { + const request = remote.requestGatewayBalances({ + account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', + hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', + strict: true + }); + + assert.deepEqual(request.message, { + command: 'gateway_balances', + id: undefined, + account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', + hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', + strict: true + }); + }); + it('Construct Payment transaction', function() { const tx = remote.createTransaction('Payment', { account: TX_JSON.Account, From 806a4e823f7543cd5989850dbf7c61ba201522c5 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Tue, 22 Sep 2015 07:59:27 -0700 Subject: [PATCH 16/22] Add getBalanceSheet() to RippleAPI --- src/api/common/schema-validator.js | 2 + .../common/schemas/balance-sheet-options.json | 15 ++++ src/api/common/schemas/get-balance-sheet.json | 67 ++++++++++++++++++ src/api/common/validate.js | 1 + src/api/index.js | 2 + src/api/ledger/balance-sheet.js | 69 +++++++++++++++++++ test/api-test.js | 6 +- .../api/responses/get-balance-sheet.json | 68 ++++++++++++++++++ test/fixtures/api/responses/index.js | 1 + test/fixtures/api/rippled/balance-sheet.json | 52 ++++++++++++++ test/fixtures/api/rippled/index.js | 1 + test/mock-rippled.js | 4 ++ test/remote-test.js | 2 +- 13 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 src/api/common/schemas/balance-sheet-options.json create mode 100644 src/api/common/schemas/get-balance-sheet.json create mode 100644 src/api/ledger/balance-sheet.js create mode 100644 test/fixtures/api/responses/get-balance-sheet.json create mode 100644 test/fixtures/api/rippled/balance-sheet.json diff --git a/src/api/common/schema-validator.js b/src/api/common/schema-validator.js index db4f99db..0f16470c 100644 --- a/src/api/common/schema-validator.js +++ b/src/api/common/schema-validator.js @@ -31,6 +31,8 @@ function loadSchemas() { require('./schemas/currency.json'), require('./schemas/get-account-info.json'), require('./schemas/get-balances.json'), + require('./schemas/get-balance-sheet'), + require('./schemas/balance-sheet-options.json'), require('./schemas/get-ledger.json'), require('./schemas/get-orderbook.json'), require('./schemas/get-orders.json'), diff --git a/src/api/common/schemas/balance-sheet-options.json b/src/api/common/schemas/balance-sheet-options.json new file mode 100644 index 00000000..85a8547c --- /dev/null +++ b/src/api/common/schemas/balance-sheet-options.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "balance-sheet-options", + "description": "Options for getBalanceSheet", + "type": "object", + "properties": { + "excludeAddresses": { + "type": "array", + "items": {"$ref": "address"}, + "uniqueItems": true + }, + "ledgerVersion": {"$ref": "ledgerVersion"} + }, + "additionalProperties": false +} diff --git a/src/api/common/schemas/get-balance-sheet.json b/src/api/common/schemas/get-balance-sheet.json new file mode 100644 index 00000000..bca25cfb --- /dev/null +++ b/src/api/common/schemas/get-balance-sheet.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "get-balance-sheet", + "description": "getBalanceSheet response", + "type": "object", + "properties": { + "balances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "required": ["counterparty", "balances"], + "additionalProperties": false, + "counterparty": {"$ref": "address"}, + "balances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "required": ["currency", "value"], + "additionalProperties": false, + "currency": {"$ref": "currency"}, + "value": {"$ref": "value"} + } + } + } + } + } + }, + "assets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "required": ["counterparty", "assets"], + "additionalProperties": false, + "counterparty": {"$ref": "address"}, + "assets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "required": ["currency", "value"], + "additionalProperties": false, + "currency": {"$ref": "currency"}, + "value": {"$ref": "value"} + } + } + } + } + } + }, + "obligations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "required": ["currency", "value"], + "additionalProperties": false, + "currency": {"$ref": "currency"}, + "value": {"$ref": "value"} + } + } + } + }, + "additionalProperties": false +} diff --git a/src/api/common/validate.js b/src/api/common/validate.js index 3c700080..ce5f2d2e 100644 --- a/src/api/common/validate.js +++ b/src/api/common/validate.js @@ -82,6 +82,7 @@ module.exports = { getAccountInfoOptions: _.partial(validateOptions, 'settings-options'), getTrustlinesOptions: _.partial(validateOptions, 'trustlines-options'), getBalancesOptions: _.partial(validateOptions, 'trustlines-options'), + getBalanceSheetOptions: _.partial(validateOptions, 'balance-sheet-options'), getOrdersOptions: _.partial(validateOptions, 'orders-options'), getOrderbookOptions: _.partial(validateOptions, 'orders-options'), getTransactionOptions: _.partial(validateOptions, 'transaction-options'), diff --git a/src/api/index.js b/src/api/index.js index 0fc6ee5c..d5a6b983 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -16,6 +16,7 @@ const getTransaction = require('./ledger/transaction'); const getTransactions = require('./ledger/transactions'); const getTrustlines = require('./ledger/trustlines'); const getBalances = require('./ledger/balances'); +const getBalanceSheet = require('./ledger/balance-sheet'); const getPaths = require('./ledger/pathfind'); const getOrders = require('./ledger/orders'); const getOrderbook = require('./ledger/orderbook'); @@ -66,6 +67,7 @@ _.assign(RippleAPI.prototype, { getTransactions, getTrustlines, getBalances, + getBalanceSheet, getPaths, getOrders, getOrderbook, diff --git a/src/api/ledger/balance-sheet.js b/src/api/ledger/balance-sheet.js new file mode 100644 index 00000000..626c71ef --- /dev/null +++ b/src/api/ledger/balance-sheet.js @@ -0,0 +1,69 @@ +'use strict'; + +const _ = require('lodash'); +const utils = require('./utils'); +const validate = utils.common.validate; +const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; + +function formatBalanceSheet({balances, obligations, assets}) { + const result = {}; + + if (!_.isUndefined(balances)) { + result.balances = Object.keys(balances).map((k) => { + return { + counterparty: k, + balances: balances[k] + }; + }); + } + if (!_.isUndefined(assets)) { + result.assets = Object.keys(assets).map((k) => { + return { + counterparty: k, + assets: assets[k] + }; + }); + } + if (!_.isUndefined(obligations)) { + result.obligations = Object.keys(obligations).map((k) => { + return {currency: k, value: obligations[k]}; + }); + } + + return result; +} + +function getBalanceSheetAsync(address, options, callback) { + validate.address(address); + validate.getBalanceSheetOptions(options); + + const requestOptions = Object.assign({}, { + account: address, + strict: true, + hotwallet: options.excludeAddresses, + ledger: options.ledgerVersion + }); + + const requestCallback = composeAsync( + formatBalanceSheet, convertErrors(callback)); + + this.remote.getLedgerSequence((err, ledgerVersion) => { + if (err) { + callback(err); + return; + } + + if (_.isUndefined(requestOptions.ledger)) { + requestOptions.ledger = ledgerVersion; + } + + this.remote.requestGatewayBalances(requestOptions, requestCallback); + }); +} + +function getBalanceSheet(address: string, options = {}) { + return utils.promisify(getBalanceSheetAsync).call(this, address, options); +} + +module.exports = getBalanceSheet; diff --git a/test/api-test.js b/test/api-test.js index 16a8fc6a..d14ebd36 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -29,7 +29,6 @@ const orderbook = { }; function checkResult(expected, schemaName, response) { - // console.log(JSON.stringify(response, null, 2)); assert.deepEqual(response, expected); if (schemaName) { schemaValidator.schemaValidate(schemaName, response); @@ -202,6 +201,11 @@ describe('RippleAPI', function() { _.partial(checkResult, responses.getBalances, 'getBalances')); }); + it('getBalanceSheet', function() { + return this.api.getBalanceSheet(address).then( + _.partial(checkResult, responses.getBalanceSheet, undefined)); + }); + describe('getTransaction', () => { it('getTransaction - payment', function() { return this.api.getTransaction(hashes.VALID_TRANSACTION_HASH).then( diff --git a/test/fixtures/api/responses/get-balance-sheet.json b/test/fixtures/api/responses/get-balance-sheet.json new file mode 100644 index 00000000..d5d8473c --- /dev/null +++ b/test/fixtures/api/responses/get-balance-sheet.json @@ -0,0 +1,68 @@ +{ + "balances": [ + { + "counterparty": "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ", + "balances": [ + { + "currency": "EUR", + "value": "29826.1965999999" + }, + { + "currency": "USD", + "value": "10.0" + } + ] + }, + { + "counterparty": "ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt", + "balances": [ + { + "currency": "USD", + "value": "13857.70416" + } + ] + } + ], + "assets": [ + { + "counterparty": "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH", + "assets": [ + { + "currency": "BTC", + "value": "5444166510000000e-26" + }, + { + "currency": "USD", + "value": "100.0" + } + ] + }, + { + "counterparty": "rwmUaXsWtXU4Z843xSYwgt1is97bgY8yj6", + "assets": [ + { + "currency": "BTC", + "value": "8700000000000000e-30" + } + ] + } + ], + "obligations": [ + { + "currency": "BTC", + "value": "5908.324927635318" + }, + { + "currency": "EUR", + "value": "992471.7419793958" + }, + { + "currency": "GBP", + "value": "4991.38706013193" + }, + { + "currency": "USD", + "value": "1997134.20229482" + } + ] +} diff --git a/test/fixtures/api/responses/index.js b/test/fixtures/api/responses/index.js index 5080660a..0770fd6f 100644 --- a/test/fixtures/api/responses/index.js +++ b/test/fixtures/api/responses/index.js @@ -4,6 +4,7 @@ module.exports = { generateAddress: require('./generate-address.json'), getAccountInfo: require('./get-account-info.json'), getBalances: require('./get-balances.json'), + getBalanceSheet: require('./get-balance-sheet.json'), getOrderbook: require('./get-orderbook.json'), getOrders: require('./get-orders.json'), getPaths: { diff --git a/test/fixtures/api/rippled/balance-sheet.json b/test/fixtures/api/rippled/balance-sheet.json new file mode 100644 index 00000000..26b5518d --- /dev/null +++ b/test/fixtures/api/rippled/balance-sheet.json @@ -0,0 +1,52 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "assets": { + "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH": [ + { + "currency": "BTC", + "value": "5444166510000000e-26" + }, + { + "currency": "USD", + "value": "100.0" + } + ], + "rwmUaXsWtXU4Z843xSYwgt1is97bgY8yj6": [ + { + "currency": "BTC", + "value": "8700000000000000e-30" + } + ] + }, + "balances": { + "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ": [ + { + "currency": "EUR", + "value": "29826.1965999999" + }, + { + "currency": "USD", + "value": "10.0" + } + ], + "ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt": [ + { + "currency": "USD", + "value": "13857.70416" + } + ] + }, + "obligations": { + "BTC": "5908.324927635318", + "EUR": "992471.7419793958", + "GBP": "4991.38706013193", + "USD": "1997134.20229482" + }, + "ledger_current_index": 9592219, + "validated": true + } +} diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index fdac6f4e..b9c66381 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -16,6 +16,7 @@ module.exports = { }, account_offers: require('./account-offers'), account_tx: require('./account-tx'), + balance_sheet: require('./balance-sheet'), book_offers: require('./book-offers'), server_info: require('./server-info'), server_info_error: require('./server-info-error'), diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 3db74eb6..b50796b6 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -283,5 +283,9 @@ module.exports = function(port) { setTimeout(() => conn.send(response), 20); }); + mock.on('request_gateway_balances', function(request, conn) { + conn.send(createResponse(request, fixtures.balance_sheet)); + }); + return mock; }; diff --git a/test/remote-test.js b/test/remote-test.js index 81ae697c..4307cf5c 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -1966,7 +1966,7 @@ describe('Remote', function() { }); }); - it.only('Construct gateway_balances request', function() { + it('Construct gateway_balances request', function() { const request = remote.requestGatewayBalances({ account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', From 772f79ae21371e137095e90ae7e3a72f812e2244 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Mon, 5 Oct 2015 12:16:25 -0700 Subject: [PATCH 17/22] Update schema --- src/api/common/schemas/get-balance-sheet.json | 22 +++++++++---------- src/api/ledger/balance-sheet.js | 21 +++++------------- test/api-test.js | 2 +- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/api/common/schemas/get-balance-sheet.json b/src/api/common/schemas/get-balance-sheet.json index bca25cfb..5e3a9a21 100644 --- a/src/api/common/schemas/get-balance-sheet.json +++ b/src/api/common/schemas/get-balance-sheet.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "get-balance-sheet", + "title": "getBalanceSheet", "description": "getBalanceSheet response", "type": "object", "properties": { @@ -8,17 +8,17 @@ "type": "array", "items": { "type": "object", + "required": ["counterparty", "balances"], + "additionalProperties": false, "properties": { - "required": ["counterparty", "balances"], - "additionalProperties": false, "counterparty": {"$ref": "address"}, "balances": { "type": "array", "items": { "type": "object", + "required": ["currency", "value"], + "additionalProperties": false, "properties": { - "required": ["currency", "value"], - "additionalProperties": false, "currency": {"$ref": "currency"}, "value": {"$ref": "value"} } @@ -31,17 +31,17 @@ "type": "array", "items": { "type": "object", + "required": ["counterparty", "assets"], + "additionalProperties": false, "properties": { - "required": ["counterparty", "assets"], - "additionalProperties": false, "counterparty": {"$ref": "address"}, "assets": { "type": "array", "items": { "type": "object", + "required": ["currency", "value"], + "additionalProperties": false, "properties": { - "required": ["currency", "value"], - "additionalProperties": false, "currency": {"$ref": "currency"}, "value": {"$ref": "value"} } @@ -54,9 +54,9 @@ "type": "array", "items": { "type": "object", + "required": ["currency", "value"], + "additionalProperties": false, "properties": { - "required": ["currency", "value"], - "additionalProperties": false, "currency": {"$ref": "currency"}, "value": {"$ref": "value"} } diff --git a/src/api/ledger/balance-sheet.js b/src/api/ledger/balance-sheet.js index 626c71ef..8709dec3 100644 --- a/src/api/ledger/balance-sheet.js +++ b/src/api/ledger/balance-sheet.js @@ -10,25 +10,16 @@ function formatBalanceSheet({balances, obligations, assets}) { const result = {}; if (!_.isUndefined(balances)) { - result.balances = Object.keys(balances).map((k) => { - return { - counterparty: k, - balances: balances[k] - }; - }); + result.balances = _.map(balances, (balances, counterparty) => + ({counterparty, balances})); } if (!_.isUndefined(assets)) { - result.assets = Object.keys(assets).map((k) => { - return { - counterparty: k, - assets: assets[k] - }; - }); + result.assets = _.map(assets, (assets, counterparty) => + ({counterparty, assets})); } if (!_.isUndefined(obligations)) { - result.obligations = Object.keys(obligations).map((k) => { - return {currency: k, value: obligations[k]}; - }); + result.obligations = _.map(obligations, (value, currency) => + ({currency, value})); } return result; diff --git a/test/api-test.js b/test/api-test.js index d14ebd36..53fc4370 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -203,7 +203,7 @@ describe('RippleAPI', function() { it('getBalanceSheet', function() { return this.api.getBalanceSheet(address).then( - _.partial(checkResult, responses.getBalanceSheet, undefined)); + _.partial(checkResult, responses.getBalanceSheet, 'getBalanceSheet')); }); describe('getTransaction', () => { From 78eeb4032266fe2f75a4043ff2cc9432a3be94a6 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Mon, 5 Oct 2015 12:20:15 -0700 Subject: [PATCH 18/22] Change rippled test fixture name --- .../api/rippled/{balance-sheet.json => gateway-balances.json} | 0 test/fixtures/api/rippled/index.js | 2 +- test/mock-rippled.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename test/fixtures/api/rippled/{balance-sheet.json => gateway-balances.json} (100%) diff --git a/test/fixtures/api/rippled/balance-sheet.json b/test/fixtures/api/rippled/gateway-balances.json similarity index 100% rename from test/fixtures/api/rippled/balance-sheet.json rename to test/fixtures/api/rippled/gateway-balances.json diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index b9c66381..a8ca70e4 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -16,7 +16,7 @@ module.exports = { }, account_offers: require('./account-offers'), account_tx: require('./account-tx'), - balance_sheet: require('./balance-sheet'), + gateway_balances: require('./gateway-balances'), book_offers: require('./book-offers'), server_info: require('./server-info'), server_info_error: require('./server-info-error'), diff --git a/test/mock-rippled.js b/test/mock-rippled.js index b50796b6..ce5abc0c 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -284,7 +284,7 @@ module.exports = function(port) { }); mock.on('request_gateway_balances', function(request, conn) { - conn.send(createResponse(request, fixtures.balance_sheet)); + conn.send(createResponse(request, fixtures.gateway_balances)); }); return mock; From 607777f2a316a79809d3b62eb9c36630abff9013 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Mon, 5 Oct 2015 12:30:41 -0700 Subject: [PATCH 19/22] Lint --- src/api/ledger/balance-sheet.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/ledger/balance-sheet.js b/src/api/ledger/balance-sheet.js index 8709dec3..54d75176 100644 --- a/src/api/ledger/balance-sheet.js +++ b/src/api/ledger/balance-sheet.js @@ -6,19 +6,19 @@ const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; const convertErrors = utils.common.convertErrors; -function formatBalanceSheet({balances, obligations, assets}) { +function formatBalanceSheet(balanceSheet) { const result = {}; - if (!_.isUndefined(balances)) { - result.balances = _.map(balances, (balances, counterparty) => + if (!_.isUndefined(balanceSheet.balances)) { + result.balances = _.map(balanceSheet.balances, (balances, counterparty) => ({counterparty, balances})); } - if (!_.isUndefined(assets)) { - result.assets = _.map(assets, (assets, counterparty) => + if (!_.isUndefined(balanceSheet.assets)) { + result.assets = _.map(balanceSheet.assets, (assets, counterparty) => ({counterparty, assets})); } - if (!_.isUndefined(obligations)) { - result.obligations = _.map(obligations, (value, currency) => + if (!_.isUndefined(balanceSheet.obligations)) { + result.obligations = _.map(balanceSheet.obligations, (value, currency) => ({currency, value})); } From ff2ac6c3cda0dfe97fe45c51351978cf0b68e3e6 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Mon, 5 Oct 2015 13:09:04 -0700 Subject: [PATCH 20/22] Format balancesheet schema --- src/api/common/schemas/get-balance-sheet.json | 32 ++------- src/api/ledger/balance-sheet.js | 16 +++-- .../api/responses/get-balance-sheet.json | 70 ++++++++----------- 3 files changed, 46 insertions(+), 72 deletions(-) diff --git a/src/api/common/schemas/get-balance-sheet.json b/src/api/common/schemas/get-balance-sheet.json index 5e3a9a21..f8b3e934 100644 --- a/src/api/common/schemas/get-balance-sheet.json +++ b/src/api/common/schemas/get-balance-sheet.json @@ -8,22 +8,12 @@ "type": "array", "items": { "type": "object", - "required": ["counterparty", "balances"], + "required": ["counterparty", "currency", "value"], "additionalProperties": false, "properties": { "counterparty": {"$ref": "address"}, - "balances": { - "type": "array", - "items": { - "type": "object", - "required": ["currency", "value"], - "additionalProperties": false, - "properties": { - "currency": {"$ref": "currency"}, - "value": {"$ref": "value"} - } - } - } + "currency": {"$ref": "currency"}, + "value": {"$ref": "value"} } } }, @@ -31,22 +21,12 @@ "type": "array", "items": { "type": "object", - "required": ["counterparty", "assets"], + "required": ["counterparty", "currency", "value"], "additionalProperties": false, "properties": { "counterparty": {"$ref": "address"}, - "assets": { - "type": "array", - "items": { - "type": "object", - "required": ["currency", "value"], - "additionalProperties": false, - "properties": { - "currency": {"$ref": "currency"}, - "value": {"$ref": "value"} - } - } - } + "currency": {"$ref": "currency"}, + "value": {"$ref": "value"} } } }, diff --git a/src/api/ledger/balance-sheet.js b/src/api/ledger/balance-sheet.js index 54d75176..608ae508 100644 --- a/src/api/ledger/balance-sheet.js +++ b/src/api/ledger/balance-sheet.js @@ -10,12 +10,20 @@ function formatBalanceSheet(balanceSheet) { const result = {}; if (!_.isUndefined(balanceSheet.balances)) { - result.balances = _.map(balanceSheet.balances, (balances, counterparty) => - ({counterparty, balances})); + result.balances = []; + _.forEach(balanceSheet.balances, (balances, counterparty) => { + _.forEach(balances, (balance) => { + result.balances.push(Object.assign({counterparty}, balance)); + }); + }); } if (!_.isUndefined(balanceSheet.assets)) { - result.assets = _.map(balanceSheet.assets, (assets, counterparty) => - ({counterparty, assets})); + result.assets = []; + _.forEach(balanceSheet.assets, (assets, counterparty) => { + _.forEach(assets, (balance) => { + result.assets.push(Object.assign({counterparty}, balance)); + }); + }); } if (!_.isUndefined(balanceSheet.obligations)) { result.obligations = _.map(balanceSheet.obligations, (value, currency) => diff --git a/test/fixtures/api/responses/get-balance-sheet.json b/test/fixtures/api/responses/get-balance-sheet.json index d5d8473c..650f07c3 100644 --- a/test/fixtures/api/responses/get-balance-sheet.json +++ b/test/fixtures/api/responses/get-balance-sheet.json @@ -1,51 +1,37 @@ { "balances": [ { - "counterparty": "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ", - "balances": [ - { - "currency": "EUR", - "value": "29826.1965999999" - }, - { - "currency": "USD", - "value": "10.0" - } - ] - }, - { - "counterparty": "ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt", - "balances": [ - { - "currency": "USD", - "value": "13857.70416" - } - ] - } + "counterparty": "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ", + "currency": "EUR", + "value": "29826.1965999999" + }, + { + "counterparty": "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ", + "currency": "USD", + "value": "10.0" + }, + { + "counterparty": "ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt", + "currency": "USD", + "value": "13857.70416" + } ], "assets": [ { - "counterparty": "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH", - "assets": [ - { - "currency": "BTC", - "value": "5444166510000000e-26" - }, - { - "currency": "USD", - "value": "100.0" - } - ] - }, - { - "counterparty": "rwmUaXsWtXU4Z843xSYwgt1is97bgY8yj6", - "assets": [ - { - "currency": "BTC", - "value": "8700000000000000e-30" - } - ] - } + "counterparty": "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH", + "currency": "BTC", + "value": "5444166510000000e-26" + }, + { + "counterparty": "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH", + "currency": "USD", + "value": "100.0" + }, + { + "counterparty": "rwmUaXsWtXU4Z843xSYwgt1is97bgY8yj6", + "currency": "BTC", + "value": "8700000000000000e-30" + } ], "obligations": [ { From 225ca3f85250960a799e19f15868d5ad526ac180 Mon Sep 17 00:00:00 2001 From: Ivan Tivonenko Date: Fri, 2 Oct 2015 04:22:51 +0300 Subject: [PATCH 21/22] BREAKING CHANGE: removed 'timeout' method of Request added default timeout to Request - will emit 'timeout' event and RippleError('tejTimeout') to callback --- src/core/request.js | 56 ++++++++++++---------------------- src/core/transactionmanager.js | 3 +- test/request-test.js | 51 ++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/core/request.js b/src/core/request.js index ea9046d4..56ad3e90 100644 --- a/src/core/request.js +++ b/src/core/request.js @@ -34,14 +34,15 @@ function Request(remote, command) { command: command, id: undefined }; + this._timeout = this.remote.submission_timeout; } util.inherits(Request, EventEmitter); // Send the request to a remote. Request.prototype.request = function(servers, callback_) { - const self = this; const callback = typeof servers === 'function' ? servers : callback_; + const self = this; if (this.requested) { throw new Error('Already requested'); @@ -70,17 +71,25 @@ Request.prototype.request = function(servers, callback_) { } } - function onReconnect() { - doRequest(); - } + const timeout = setTimeout(() => { + if (typeof callback === 'function') { + callback(new RippleError('tejTimeout')); + } + + this.emit('timeout'); + // just in case + this.emit = _.noop; + this.cancel(); + }, this._timeout); function onResponse() { - self.remote.removeListener('connected', onReconnect); + clearTimeout(timeout); } if (this.remote.isConnected()) { - this.remote.on('connected', onReconnect); + this.remote.on('connected', doRequest); } + this.once('response', onResponse); doRequest(); @@ -264,38 +273,11 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) { return this; }; -Request.prototype.timeout = function(duration, callback) { - const self = this; - - function requested() { - self.timeout(duration, callback); +Request.prototype.setTimeout = function(delay) { + if (!_.isFinite(delay)) { + throw new Error('delay must be number'); } - - if (!this.requested) { - // Defer until requested - return this.once('request', requested); - } - - const emit = this.emit; - let timed_out = false; - - const timeout = setTimeout(function() { - timed_out = true; - - if (typeof callback === 'function') { - callback(); - } - - emit.call(self, 'timeout'); - self.cancel(); - }, duration); - - this.emit = function() { - if (!timed_out) { - clearTimeout(timeout); - emit.apply(self, arguments); - } - }; + this._timeout = delay; return this; }; diff --git a/src/core/transactionmanager.js b/src/core/transactionmanager.js index 872f61a1..c741bf26 100644 --- a/src/core/transactionmanager.js +++ b/src/core/transactionmanager.js @@ -698,7 +698,8 @@ TransactionManager.prototype._request = function(tx) { tx.emit('postsubmit'); - submitRequest.timeout(self._submissionTimeout, requestTimeout); + submitRequest.setTimeout(self._submissionTimeout); + submitRequest.once('timeout', requestTimeout); }; /** diff --git a/test/request-test.js b/test/request-test.js index a606bba6..667c2225 100644 --- a/test/request-test.js +++ b/test/request-test.js @@ -65,6 +65,38 @@ describe('Request', function() { }); + it('Send request - reconnect', function(done) { + const server = makeServer('wss://localhost:5006'); + let emitted = 0; + + const remote = new Remote(); + remote._connected = true; + remote._servers = [server]; + + server._request = function(req) { + assert(req instanceof Request); + assert.strictEqual(typeof req.message, 'object'); + assert.strictEqual(req.message.command, 'server_info'); + if (++emitted === 1) { + setTimeout(function() { + remote.emit('connected'); + }, 2); + } if (emitted === 2) { + setTimeout(function() { + req.emit('success', SERVER_INFO); + req.emit('response', SERVER_INFO); + }, 2); + } + }; + + const request = new Request(remote, 'server_info'); + + request.callback(function() { + assert.strictEqual(emitted, 2); + done(); + }); + }); + it('Send request -- filterRequest', function(done) { const servers = [ makeServer('wss://localhost:5006'), @@ -538,6 +570,7 @@ describe('Request', function() { setTimeout(function() { successEmitted = true; req.emit('success', SERVER_INFO); + req.emit('response', SERVER_INFO); }, 200); }; @@ -546,8 +579,9 @@ describe('Request', function() { remote._servers = [server]; const request = new Request(remote, 'server_info'); + request.setTimeout(10); - request.timeout(10, function() { + request.on('timeout', function() { setTimeout(function() { assert(successEmitted); done(); @@ -568,7 +602,8 @@ describe('Request', function() { assert.strictEqual(req.message.command, 'server_info'); setTimeout(function() { req.emit('success', SERVER_INFO); - }, 200); + req.emit('response', SERVER_INFO); + }, 20); }; const remote = new Remote(); @@ -583,13 +618,15 @@ describe('Request', function() { timedOut = true; }); - request.timeout(1000); + request.setTimeout(100); request.callback(function(err, res) { - assert(!timedOut); - assert.ifError(err); - assert.deepEqual(res, SERVER_INFO); - done(); + setTimeout(function() { + assert(!timedOut, 'must not timeout'); + assert.ifError(err); + assert.deepEqual(res, SERVER_INFO); + done(); + }, 100); }); }); From 64baef431df9b1ee0a5a9f7b4b0f4187f9746583 Mon Sep 17 00:00:00 2001 From: wltsmrz Date: Mon, 5 Oct 2015 13:37:55 -0700 Subject: [PATCH 22/22] Use amount schema --- src/api/common/schemas/get-balance-sheet.json | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/api/common/schemas/get-balance-sheet.json b/src/api/common/schemas/get-balance-sheet.json index f8b3e934..5d74494a 100644 --- a/src/api/common/schemas/get-balance-sheet.json +++ b/src/api/common/schemas/get-balance-sheet.json @@ -6,29 +6,11 @@ "properties": { "balances": { "type": "array", - "items": { - "type": "object", - "required": ["counterparty", "currency", "value"], - "additionalProperties": false, - "properties": { - "counterparty": {"$ref": "address"}, - "currency": {"$ref": "currency"}, - "value": {"$ref": "value"} - } - } + "items": {"$ref": "amount"} }, "assets": { "type": "array", - "items": { - "type": "object", - "required": ["counterparty", "currency", "value"], - "additionalProperties": false, - "properties": { - "counterparty": {"$ref": "address"}, - "currency": {"$ref": "currency"}, - "value": {"$ref": "value"} - } - } + "items": {"$ref": "amount"} }, "obligations": { "type": "array",