diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b2fe3001..31b640ba 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,32 +9,22 @@ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" }, "babel-runtime": { - "version": "5.8.25", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.25.tgz", + "version": "5.8.29", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz", "dependencies": { "core-js": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.1.4.tgz" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.3.tgz" } } }, "bignumber.js": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.0.tgz" }, "bn.js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.1.2.tgz" - }, - "es6-promisify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-2.0.0.tgz", - "dependencies": { - "es6-promise": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz" - } - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz" }, "extend": { "version": "1.2.1", @@ -121,8 +111,8 @@ "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz", "dependencies": { "x-address-codec": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.0.tgz", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.2.tgz", "dependencies": { "base-x": { "version": "1.0.1", @@ -136,17 +126,13 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz", "dependencies": { - "bn.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz" - }, "create-hash": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", "dependencies": { "cipher-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz" }, "ripemd160": { "version": "1.0.1", @@ -177,8 +163,8 @@ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", "dependencies": { "cipher-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz" }, "inherits": { "version": "2.0.1", @@ -205,8 +191,8 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz" }, "elliptic": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.1.0.tgz", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.2.0.tgz", "dependencies": { "inherits": { "version": "2.0.1", @@ -222,7 +208,13 @@ }, "ripple-lib-value": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ripple-lib-value/-/ripple-lib-value-0.1.0.tgz" + "resolved": "https://registry.npmjs.org/ripple-lib-value/-/ripple-lib-value-0.1.0.tgz", + "dependencies": { + "bignumber.js": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.8.tgz" + } + } }, "sjcl-codec": { "version": "0.1.0", @@ -232,20 +224,6 @@ "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" @@ -253,20 +231,6 @@ "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 aa79b0a3..54592277 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "babel-runtime": "^5.5.4", "bignumber.js": "^2.0.3", "bn.js": "^3.1.1", - "es6-promisify": "^2.0.0", "extend": "~1.2.1", "hash.js": "^1.0.3", "https-proxy-agent": "^1.0.0", @@ -39,7 +38,7 @@ "assert-diff": "^1.0.1", "babel": "^5.8.21", "babel-core": "^5.8.22", - "babel-eslint": "^4.0.5", + "babel-eslint": "^4.1.3", "babel-loader": "^5.3.2", "coveralls": "~2.10.0", "eslint": "^1.3.0", diff --git a/src/api/common/connection.js b/src/api/common/connection.js index 55e6fabb..7030ccac 100644 --- a/src/api/common/connection.js +++ b/src/api/common/connection.js @@ -2,7 +2,9 @@ const {EventEmitter} = require('events'); const WebSocket = require('ws'); // temporary: RangeSet will be moved to api/common soon -const RangeSet = require('./utils').core._test.RangeSet; +const RangeSet = require('./rangeset').RangeSet; +const {RippledError, DisconnectedError, NotConnectedError, + TimeoutError, UnexpectedError} = require('./errors'); function isStreamMessageType(type) { return type === 'ledgerClosed' || @@ -10,48 +12,6 @@ function isStreamMessageType(type) { type === 'path_find'; } -class RippledError extends Error { - constructor(message) { - super(message); - this.name = this.constructor.name; - this.message = message; - Error.captureStackTrace(this, this.constructor.name); - } -} - -class ConnectionError extends Error { - constructor(message) { - super(message); - this.name = this.constructor.name; - this.message = message; - Error.captureStackTrace(this, this.constructor.name); - } -} - -class NotConnectedError extends ConnectionError { - constructor(message) { - super(message); - } -} - -class DisconnectedError extends ConnectionError { - constructor(message) { - super(message); - } -} - -class TimeoutError extends ConnectionError { - constructor(message) { - super(message); - } -} - -class UnexpectedError extends ConnectionError { - constructor(message) { - super(message); - } -} - class Connection extends EventEmitter { constructor(url, options = {}) { super(); @@ -64,31 +24,42 @@ class Connection extends EventEmitter { this._nextRequestID = 1; } - _onMessage(message) { - try { - const data = JSON.parse(message); - if (data.type === 'response') { - if (!(Number.isInteger(data.id) && data.id >= 0)) { - throw new UnexpectedError('valid id not found in response'); - } - this.emit(data.id.toString(), data); - } else if (isStreamMessageType(data.type)) { - if (data.type === 'ledgerClosed') { - this._ledgerVersion = Number(data.ledger_index); - this._availableLedgerVersions.addValue(this._ledgerVersion); - } - this.emit(data.type, data); - } else if (data.type === undefined && data.error) { - this.emit('error', data.error, data.error_message); // e.g. slowDown - } else { - throw new UnexpectedError('unrecognized message type: ' + data.type); + // return value is array of arguments to Connection.emit + _parseMessage(message) { + const data = JSON.parse(message); + if (data.type === 'response') { + if (!(Number.isInteger(data.id) && data.id >= 0)) { + throw new UnexpectedError('valid id not found in response'); } - } catch (error) { - this.emit('error', 'badMessage', message); + return [data.id.toString(), data]; + } else if (isStreamMessageType(data.type)) { + if (data.type === 'ledgerClosed') { + this._ledgerVersion = Number(data.ledger_index); + this._availableLedgerVersions.reset(); + this._availableLedgerVersions.parseAndAddRanges( + data.validated_ledgers); + } + return [data.type, data]; + } else if (data.type === undefined && data.error) { + return ['error', data.error, data.error_message]; // e.g. slowDown } + throw new UnexpectedError('unrecognized message type: ' + data.type); } - get state() { + _onMessage(message) { + let parameters; + try { + parameters = this._parseMessage(message); + } catch (error) { + this.emit('error', 'badMessage', message); + return; + } + // we don't want this inside the try/catch or exceptions in listener + // will be caught + this.emit.apply(this, parameters); + } + + get _state() { return this._ws ? this._ws.readyState : WebSocket.CLOSED; } @@ -96,32 +67,34 @@ class Connection extends EventEmitter { return this._ws !== null; } + isConnected() { + return this._state === WebSocket.OPEN && this._isReady; + } + _onUnexpectedClose() { this._isReady = false; this.connect().then(); } _onOpen() { - const subscribeRequest = { + const request = { command: 'subscribe', streams: ['ledger'] }; - return this.request(subscribeRequest).then(() => { - return this.request({command: 'server_info'}).then(response => { - this._ledgerVersion = Number(response.info.validated_ledger.seq); - this._availableLedgerVersions.parseAndAddRanges( - response.info.complete_ledgers); - this._isReady = true; - this.emit('connected'); - }); + return this.request(request).then(response => { + this._ledgerVersion = Number(response.ledger_index); + this._availableLedgerVersions.parseAndAddRanges( + response.validated_ledgers); + this._isReady = true; + this.emit('connected'); }); } connect() { return new Promise((resolve, reject) => { - if (this.state === WebSocket.OPEN) { + if (this._state === WebSocket.OPEN) { resolve(); - } else if (this.state === WebSocket.CONNECTING) { + } else if (this._state === WebSocket.CONNECTING) { this._ws.once('open', resolve); } else { this._ws = new WebSocket(this._url); @@ -133,10 +106,10 @@ class Connection extends EventEmitter { } disconnect() { - return new Promise((resolve) => { - if (this.state === WebSocket.CLOSED) { + return new Promise(resolve => { + if (this._state === WebSocket.CLOSED) { resolve(); - } else if (this.state === WebSocket.CLOSING) { + } else if (this._state === WebSocket.CLOSING) { this._ws.once('close', resolve); } else { this._ws.removeListener('close', this._onUnexpectedClose); @@ -158,7 +131,7 @@ class Connection extends EventEmitter { return new Promise((resolve, reject) => { if (!this._shouldBeConnected) { reject(new NotConnectedError()); - } else if (this.state === WebSocket.OPEN && this._isReady) { + } else if (this._state === WebSocket.OPEN && this._isReady) { promise.then(resolve, reject); } else { this.once('connected', () => promise.then(resolve, reject)); @@ -167,14 +140,13 @@ class Connection extends EventEmitter { } getLedgerVersion() { - return this._whenReady( - new Promise(resolve => resolve(this._ledgerVersion))); + return this._whenReady(Promise.resolve(this._ledgerVersion)); } hasLedgerVersions(lowLedgerVersion, highLedgerVersion) { - return this._whenReady(new Promise(resolve => - resolve(this._availableLedgerVersions.containsRange( - lowLedgerVersion, highLedgerVersion)))); + return this._whenReady(Promise.resolve( + this._availableLedgerVersions.containsRange( + lowLedgerVersion, highLedgerVersion || this._ledgerVersion))); } hasLedgerVersion(ledgerVersion) { @@ -214,7 +186,9 @@ class Connection extends EventEmitter { function cleanup() { clearTimeout(timer); self.removeAllListeners(eventName); - self._ws.removeListener('close', onDisconnect); + if (self._ws !== null) { + self._ws.removeListener('close', onDisconnect); + } } function _resolve(response) { diff --git a/src/api/common/constants.js b/src/api/common/constants.js index 40427cc2..412ea055 100644 --- a/src/api/common/constants.js +++ b/src/api/common/constants.js @@ -1,17 +1,26 @@ 'use strict'; -const core = require('./utils').core; const flagIndices = require('./txflags').txFlagIndices.AccountSet; -const flags = core.Remote.flags.account_root; + +const accountRootFlags = { + PasswordSpent: 0x00010000, // password set fee is spent + RequireDestTag: 0x00020000, // require a DestinationTag for payments + RequireAuth: 0x00040000, // require a authorization to hold IOUs + DisallowXRP: 0x00080000, // disallow sending XRP + DisableMaster: 0x00100000, // force regular key + NoFreeze: 0x00200000, // permanently disallowed freezing trustlines + GlobalFreeze: 0x00400000, // trustlines globally frozen + DefaultRipple: 0x00800000 +}; const AccountFlags = { - passwordSpent: flags.PasswordSpent, - requireDestinationTag: flags.RequireDestTag, - requireAuthorization: flags.RequireAuth, - disallowIncomingXRP: flags.DisallowXRP, - disableMasterKey: flags.DisableMaster, - noFreeze: flags.NoFreeze, - globalFreeze: flags.GlobalFreeze, - defaultRipple: flags.DefaultRipple + passwordSpent: accountRootFlags.PasswordSpent, + requireDestinationTag: accountRootFlags.RequireDestTag, + requireAuthorization: accountRootFlags.RequireAuth, + disallowIncomingXRP: accountRootFlags.DisallowXRP, + disableMasterKey: accountRootFlags.DisableMaster, + noFreeze: accountRootFlags.NoFreeze, + globalFreeze: accountRootFlags.GlobalFreeze, + defaultRipple: accountRootFlags.DefaultRipple }; const AccountFlagIndices = { diff --git a/src/api/common/errors.js b/src/api/common/errors.js index 71d98e91..6b9de2b3 100644 --- a/src/api/common/errors.js +++ b/src/api/common/errors.js @@ -34,6 +34,48 @@ RippleError.prototype.inspect = function(depth) { return this.toString(); }; +class RippledError extends RippleError { + constructor(message) { + super(message); + this.name = this.constructor.name; + this.message = message; + Error.captureStackTrace(this, this.constructor.name); + } +} + +class ConnectionError extends RippleError { + constructor(message) { + super(message); + this.name = this.constructor.name; + this.message = message; + Error.captureStackTrace(this, this.constructor.name); + } +} + +class NotConnectedError extends ConnectionError { + constructor(message) { + super(message); + } +} + +class DisconnectedError extends ConnectionError { + constructor(message) { + super(message); + } +} + +class TimeoutError extends ConnectionError { + constructor(message) { + super(message); + } +} + +class UnexpectedError extends ConnectionError { + constructor(message) { + super(message); + } +} + function ValidationError(message) { this.message = message; } @@ -117,5 +159,11 @@ module.exports = { MissingLedgerHistoryError, TimeOutError, ApiError, - RippleError + RippleError, + ConnectionError, + RippledError, + NotConnectedError, + DisconnectedError, + TimeoutError, + UnexpectedError }; diff --git a/src/api/common/index.js b/src/api/common/index.js index ef0a74f7..7c65dfb6 100644 --- a/src/api/common/index.js +++ b/src/api/common/index.js @@ -3,7 +3,6 @@ const utils = require('./utils'); module.exports = { Connection: require('./connection'), - core: utils.core, constants: require('./constants'), errors: require('./errors'), validate: require('./validate'), @@ -13,12 +12,11 @@ module.exports = { xrpToDrops: utils.xrpToDrops, toRippledAmount: utils.toRippledAmount, generateAddress: utils.generateAddress, - composeAsync: utils.composeAsync, - wrapCatch: utils.wrapCatch, + generateAddressAPI: utils.generateAddressAPI, removeUndefined: utils.removeUndefined, - convertErrors: utils.convertErrors, convertExceptions: utils.convertExceptions, convertKeysFromSnakeCaseToCamelCase: utils.convertKeysFromSnakeCaseToCamelCase, - promisify: utils.promisify + rippleToUnixTimestamp: utils.rippleToUnixTimestamp, + unixToRippleTimestamp: utils.unixToRippleTimestamp }; diff --git a/src/api/common/rangeset.js b/src/api/common/rangeset.js new file mode 100644 index 00000000..391383c7 --- /dev/null +++ b/src/api/common/rangeset.js @@ -0,0 +1,61 @@ +/* @flow */ +'use strict'; +const _ = require('lodash'); +const assert = require('assert'); +const ranges = Symbol(); + +function mergeIntervals(intervals: Array<[number, number]>) { + const stack = [[-Infinity, -Infinity]]; + _.forEach(_.sortBy(intervals, x => x[0]), interval => { + const lastInterval = stack.pop(); + if (interval[0] <= lastInterval[1] + 1) { + stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])]); + } else { + stack.push(lastInterval); + stack.push(interval); + } + }); + return stack.slice(1); +} + +class RangeSet { + constructor() { + this.reset(); + } + + reset() { + this[ranges] = []; + } + + serialize() { + return this[ranges].map(range => + range[0].toString() + '-' + range[1].toString()).join(','); + } + + addRange(start: number, end: number) { + assert(start <= end, 'invalid range'); + this[ranges] = mergeIntervals(this[ranges].concat([[start, end]])); + } + + addValue(value: number) { + this.addRange(value, value); + } + + parseAndAddRanges(rangesString: string) { + const rangeStrings = rangesString.split(','); + _.forEach(rangeStrings, rangeString => { + const range = rangeString.split('-').map(Number); + this.addRange(range[0], range.length === 1 ? range[0] : range[1]); + }); + } + + containsRange(start: number, end: number) { + return _.some(this[ranges], range => range[0] <= start && range[1] >= end); + } + + containsValue(value: number) { + return this.containsRange(value, value); + } +} + +module.exports.RangeSet = RangeSet; diff --git a/src/api/common/schema-validator.js b/src/api/common/schema-validator.js index 427dfebf..3df2f89c 100644 --- a/src/api/common/schema-validator.js +++ b/src/api/common/schema-validator.js @@ -53,7 +53,7 @@ function loadSchemas() { require('./schemas/payment-transaction.json'), require('./schemas/payment.json'), require('./schemas/quality.json'), - require('./schemas/remote-options.json'), + require('./schemas/api-options.json'), require('./schemas/sequence.json'), require('./schemas/settings-options.json'), require('./schemas/settings-transaction.json'), diff --git a/src/api/common/schemas/remote-options.json b/src/api/common/schemas/api-options.json similarity index 93% rename from src/api/common/schemas/remote-options.json rename to src/api/common/schemas/api-options.json index a1cc02be..f7fbecc0 100644 --- a/src/api/common/schemas/remote-options.json +++ b/src/api/common/schemas/api-options.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "remote-options", + "title": "api-options", "type": "object", "properties": { "trace": {"type": "boolean"}, diff --git a/src/api/common/serverinfo.js b/src/api/common/serverinfo.js index d209950f..05a64368 100644 --- a/src/api/common/serverinfo.js +++ b/src/api/common/serverinfo.js @@ -1,7 +1,7 @@ 'use strict'; const _ = require('lodash'); -const {RippledNetworkError} = require('./errors'); -const {promisify, convertKeysFromSnakeCaseToCamelCase} = require('./utils'); +const {convertKeysFromSnakeCaseToCamelCase} = require('./utils'); +import type {Connection} from './connection'; export type GetServerInfoResponse = { buildVersion: string, @@ -32,21 +32,10 @@ export type GetServerInfoResponse = { validationQuorum: number } -function getServerInfoAsync(remote, - callback: (err: any, data?: GetServerInfoResponse) => void -): void { - remote.rawRequest({command: 'server_info'}, (error, response) => { - if (error) { - const message = _.get(error, ['remote', 'error_message'], error.message); - callback(new RippledNetworkError(message)); - } else { - callback(null, convertKeysFromSnakeCaseToCamelCase(response.info)); - } - }); -} - -function getServerInfo(remote: Object): Promise { - return promisify(getServerInfoAsync)(remote); +function getServerInfo(connection: Connection): Promise { + return connection.request({command: 'server_info'}).then(response => + convertKeysFromSnakeCaseToCamelCase(response.info) + ); } function computeFeeFromServerInfo(cushion: number, @@ -56,8 +45,8 @@ function computeFeeFromServerInfo(cushion: number, * Number(serverInfo.loadFactor) * cushion).toString(); } -function getFee(remote: Object, cushion: number) { - return getServerInfo(remote).then( +function getFee(connection: Connection, cushion: number) { + return getServerInfo(connection).then( _.partial(computeFeeFromServerInfo, cushion)); } diff --git a/src/api/common/utils.js b/src/api/common/utils.js index 959a6c75..1b91e648 100644 --- a/src/api/common/utils.js +++ b/src/api/common/utils.js @@ -2,9 +2,7 @@ 'use strict'; const _ = require('lodash'); const BigNumber = require('bignumber.js'); -const core = require('../../core'); const errors = require('./errors'); -const es6promisify = require('es6-promisify'); const keypairs = require('ripple-keypairs'); import type {Amount, RippledAmount} from './types.js'; @@ -36,63 +34,12 @@ function generateAddress(options?: Object): Object { return {secret, address}; } -type AsyncFunction = (...x: any) => void - -function wrapCatch(asyncFunction: AsyncFunction): AsyncFunction { - return function() { - try { - asyncFunction.apply(this, arguments); - } catch (error) { - const callback = arguments[arguments.length - 1]; - callback(error); - } - }; -} - -type Callback = (err: any, data: any) => void -type Wrapper = (data: any) => any - -function composeAsync(wrapper: Wrapper, callback: Callback): Callback { - return function(error, data) { - if (error) { - callback(error, data); - return; - } - let result; - try { - result = wrapper(data); - } catch (exception) { - callback(exception); - return; - } - callback(null, result); - }; -} - -function convertErrors(callback: Callback): () => void { - return function(error, data) { - if (error && !(error instanceof errors.RippleError)) { - const message = _.get(error, ['remote', 'error_message'], error.message); - const error_ = new errors.RippleError(message); - error_.data = data; - callback(error_, data); - } else if (error) { - error.data = data; - callback(error, data); - } else { - callback(error, data); - } - }; -} - -function convertExceptions(f: () => T): () => T { - return function() { - try { - return f.apply(this, arguments); - } catch (error) { - throw new errors.ApiError(error.message); - } - }; +function generateAddressAPI(options?: Object): Object { + try { + return generateAddress(options); + } catch (error) { + throw new errors.ApiError(error.message); + } } const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g; @@ -111,25 +58,38 @@ function convertKeysFromSnakeCaseToCamelCase(obj: any): any { return obj; } -function promisify(asyncFunction: AsyncFunction): Function { - return es6promisify(wrapCatch(asyncFunction)); -} - function removeUndefined(obj: Object): Object { return _.omit(obj, _.isUndefined); } +/** + * @param {Number} rpepoch (seconds since 1/1/2000 GMT) + * @return {Number} ms since unix epoch + * + */ +function rippleToUnixTimestamp(rpepoch: number): number { + return (rpepoch + 0x386D4380) * 1000; +} + +/** + * @param {Number|Date} timestamp (ms since unix epoch) + * @return {Number} seconds since ripple epoch ( 1/1/2000 GMT) + */ +function unixToRippleTimestamp(timestamp: number | Date): number { + const timestamp_ = timestamp instanceof Date ? + timestamp.getTime() : + timestamp; + return Math.round(timestamp_ / 1000) - 0x386D4380; +} + module.exports = { - core, dropsToXrp, xrpToDrops, toRippledAmount, generateAddress, - composeAsync, - wrapCatch, - convertExceptions, - convertErrors, + generateAddressAPI, convertKeysFromSnakeCaseToCamelCase, - promisify, - removeUndefined + removeUndefined, + rippleToUnixTimestamp, + unixToRippleTimestamp }; diff --git a/src/api/common/validate.js b/src/api/common/validate.js index ce5f2d2e..2613d126 100644 --- a/src/api/common/validate.js +++ b/src/api/common/validate.js @@ -88,6 +88,6 @@ module.exports = { getTransactionOptions: _.partial(validateOptions, 'transaction-options'), getLedgerOptions: _.partial(validateOptions, 'ledger-options'), options: _.partial(validateOptions, 'options'), - remoteOptions: _.partial(schemaValidate, 'remote-options'), + apiOptions: _.partial(schemaValidate, 'api-options'), instructions: _.partial(schemaValidate, 'instructions') }; diff --git a/src/api/index.js b/src/api/index.js index a3690730..8ce240c5 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -36,22 +36,34 @@ const prepareSettings = require('./transaction/settings'); const sign = require('./transaction/sign'); const submit = require('./transaction/submit'); const errors = require('./common').errors; -const convertExceptions = require('./common').convertExceptions; -const generateAddress = convertExceptions(common.generateAddress); +const generateAddress = common.generateAddressAPI; const computeLedgerHash = require('./offline/ledgerhash'); const getLedger = require('./ledger/ledger'); -function RippleAPI(options: {}) { - common.validate.remoteOptions(options); +type APIOptions = { + servers?: Array, + feeCushion?: number, + trace?: boolean, + proxy?: string +} + +function RippleAPI(options: APIOptions = {}) { + common.validate.apiOptions(options); if (EventEmitter instanceof Function) { // always true, needed for flow EventEmitter.call(this); } - const _options = _.assign({}, options, {automatic_resubmission: false}); - this._feeCushion = _options.feeCushion || 1.2; - this.remote = new common.core.Remote(_options); - this.remote.on('ledger_closed', message => { - this.emit('ledgerClosed', server.formatLedgerClose(message)); - }); + if (options.servers !== undefined) { + const servers: Array = options.servers; + if (servers.length === 1) { + this._feeCushion = options.feeCushion || 1.2; + this.connection = new common.Connection(servers[0], options); + this.connection.on('ledgerClosed', message => { + this.emit('ledgerClosed', server.formatLedgerClose(message)); + }); + } else { + throw new errors.RippleError('Multi-server not implemented'); + } + } } util.inherits(RippleAPI, EventEmitter); diff --git a/src/api/ledger/accountinfo.js b/src/api/ledger/accountinfo.js index 181d0b4e..efd64bdd 100644 --- a/src/api/ledger/accountinfo.js +++ b/src/api/ledger/accountinfo.js @@ -2,7 +2,7 @@ 'use strict'; const utils = require('./utils'); -const {validate, composeAsync, convertErrors, removeUndefined} = utils.common; +const {validate, removeUndefined} = utils.common; type AccountData = { Sequence: number, @@ -29,8 +29,6 @@ type AccountInfoOptions = { ledgerVersion?: number } -type AccountInfoCallback = (err: any, data: AccountInfoResponse) => void - type AccountInfoResponse = { sequence: number, xrpBalance: string, @@ -52,9 +50,8 @@ function formatAccountInfo(response: AccountDataResponse) { }); } -function getAccountInfoAsync(account: string, options: AccountInfoOptions, - callback: AccountInfoCallback -) { +function getAccountInfo(account: string, options: AccountInfoOptions = {} +): Promise { validate.address(account); validate.getAccountInfoOptions(options); @@ -64,13 +61,7 @@ function getAccountInfoAsync(account: string, options: AccountInfoOptions, ledger_index: options.ledgerVersion || 'validated' }; - this.remote.rawRequest(request, - composeAsync(formatAccountInfo, convertErrors(callback))); -} - -function getAccountInfo(account: string, options: AccountInfoOptions = {} -): Promise { - return utils.promisify(getAccountInfoAsync).call(this, account, options); + return this.connection.request(request).then(formatAccountInfo); } module.exports = getAccountInfo; diff --git a/src/api/ledger/balance-sheet.js b/src/api/ledger/balance-sheet.js index 7eb83631..f451ec52 100644 --- a/src/api/ledger/balance-sheet.js +++ b/src/api/ledger/balance-sheet.js @@ -3,7 +3,7 @@ const _ = require('lodash'); const utils = require('./utils'); -const {validate, composeAsync, convertErrors} = utils.common; +const {validate} = utils.common; import type {Amount} from '../common/types.js'; type BalanceSheetOptions = { @@ -47,42 +47,22 @@ function formatBalanceSheet(balanceSheet): GetBalanceSheet { return result; } -function getBalanceSheetAsync(address: string, options: BalanceSheetOptions, - callback -) { +function getBalanceSheet(address: string, options: BalanceSheetOptions = {} +): Promise { validate.address(address); validate.getBalanceSheetOptions(options); - const request = { - command: 'gateway_balances', - account: address, - strict: true, - hotwallet: options.excludeAddresses, - ledger_index: options.ledgerVersion - }; + return utils.ensureLedgerVersion.call(this, options).then(_options => { + const request = { + command: 'gateway_balances', + account: address, + strict: true, + hotwallet: _options.excludeAddresses, + ledger_index: _options.ledgerVersion + }; - const requestCallback = composeAsync( - formatBalanceSheet, convertErrors(callback)); - - if (_.isUndefined(request.ledger_index)) { - this.remote.getLedgerSequence((err, ledgerVersion) => { - if (err) { - convertErrors(callback)(err); - return; - } - - request.ledger_index = ledgerVersion; - - this.remote.rawRequest(request, requestCallback); - }); - } else { - this.remote.rawRequest(request, requestCallback); - } -} - -function getBalanceSheet(address: string, options: BalanceSheetOptions = {} -): Promise { - return utils.promisify(getBalanceSheetAsync).call(this, address, options); + return this.connection.request(request).then(formatBalanceSheet); + }); } module.exports = getBalanceSheet; diff --git a/src/api/ledger/balances.js b/src/api/ledger/balances.js index 3c10a3aa..96cd25d7 100644 --- a/src/api/ledger/balances.js +++ b/src/api/ledger/balances.js @@ -1,11 +1,8 @@ /* @flow */ 'use strict'; -const _ = require('lodash'); -const async = require('async'); const utils = require('./utils'); -const getTrustlines = require('./trustlines'); -const {validate, composeAsync, convertErrors} = utils.common; -import type {Remote, GetLedgerSequenceCallback} from '../../core/remote'; +const {validate} = utils.common; +import type {Connection} from '../common/connection.js'; import type {TrustlinesOptions, Trustline} from './trustlines-types.js'; @@ -43,42 +40,26 @@ function formatBalances(options, balances) { return result; } -function getTrustlinesAsync(account: string, options: TrustlinesOptions, - callback -) { - getTrustlines.call(this, account, options) - .then(data => callback(null, data)) - .catch(callback); -} - -function getLedgerVersionHelper(remote: Remote, optionValue?: number, - callback: GetLedgerSequenceCallback -) { +function getLedgerVersionHelper(connection: Connection, optionValue?: number +): Promise { if (optionValue !== undefined && optionValue !== null) { - callback(null, optionValue); - } else { - remote.getLedgerSequence(callback); + return Promise.resolve(optionValue); } -} - -function getBalancesAsync(account: string, options: TrustlinesOptions, - callback -) { - validate.address(account); - validate.getBalancesOptions(options); - - async.parallel({ - xrp: async.seq( - _.partial(getLedgerVersionHelper, this.remote, options.ledgerVersion), - _.partial(utils.getXRPBalance, this.remote, account) - ), - trustlines: _.partial(getTrustlinesAsync.bind(this), account, options) - }, composeAsync(_.partial(formatBalances, options), convertErrors(callback))); + return connection.getLedgerVersion(); } function getBalances(account: string, options: TrustlinesOptions = {} ): Promise { - return utils.promisify(getBalancesAsync).call(this, account, options); + validate.address(account); + validate.getBalancesOptions(options); + + return Promise.all([ + getLedgerVersionHelper(this.connection, options.ledgerVersion).then( + ledgerVersion => + utils.getXRPBalance(this.connection, account, ledgerVersion)), + this.getTrustlines(account, options) + ]).then(results => + formatBalances(options, {xrp: results[0], trustlines: results[1]})); } module.exports = getBalances; diff --git a/src/api/ledger/ledger.js b/src/api/ledger/ledger.js index ac874eb5..6088ce02 100644 --- a/src/api/ledger/ledger.js +++ b/src/api/ledger/ledger.js @@ -1,7 +1,7 @@ /* @flow */ 'use strict'; const utils = require('./utils'); -const {validate, composeAsync, convertErrors} = utils.common; +const {validate} = utils.common; const parseLedger = require('./parse/ledger'); import type {GetLedger} from './types.js'; @@ -13,7 +13,7 @@ type LedgerOptions = { } -function getLedgerAsync(options: LedgerOptions, callback) { +function getLedger(options: LedgerOptions = {}): Promise { validate.getLedgerOptions(options); const request = { @@ -24,13 +24,8 @@ function getLedgerAsync(options: LedgerOptions, callback) { accounts: options.includeState }; - this.remote.rawRequest(request, - composeAsync(response => parseLedger(response.ledger), - convertErrors(callback))); -} - -function getLedger(options: LedgerOptions = {}): Promise { - return utils.promisify(getLedgerAsync).call(this, options); + return this.connection.request(request).then(response => + parseLedger(response.ledger)); } module.exports = getLedger; diff --git a/src/api/ledger/orderbook.js b/src/api/ledger/orderbook.js index a469aaff..1222b922 100644 --- a/src/api/ledger/orderbook.js +++ b/src/api/ledger/orderbook.js @@ -1,11 +1,10 @@ /* @flow */ 'use strict'; const _ = require('lodash'); -const async = require('async'); const utils = require('./utils'); -const {validate, composeAsync, convertErrors} = utils.common; +const {validate} = utils.common; const parseOrderbookOrder = require('./parse/orderbook-order'); -import type {Remote} from '../../core/remote'; +import type {Connection} from '../common/connection.js'; import type {OrdersOptions, OrderSpecification} from './types.js'; import type {Amount, Issue} from '../common/types.js'; @@ -36,18 +35,18 @@ type GetOrderbook = { // account is to specify a "perspective", which affects which unfunded offers // are returned -function getBookOffers(remote: Remote, account: string, +function getBookOffers(connection: Connection, account: string, ledgerVersion?: number, limit?: number, takerGets: Issue, - takerPays: Issue, callback -) { - remote.rawRequest(utils.renameCounterpartyToIssuerInOrder({ + takerPays: Issue +): Promise { + return connection.request(utils.renameCounterpartyToIssuerInOrder({ command: 'book_offers', taker_gets: takerGets, taker_pays: takerPays, ledger_index: ledgerVersion || 'validated', limit: limit, taker: account - }), composeAsync(data => data.offers, convertErrors(callback))); + })).then(data => data.offers); } function isSameIssue(a: Amount, b: Amount) { @@ -92,27 +91,19 @@ function formatBidsAndAsks(orderbook: Orderbook, offers) { return {bids, asks}; } -function getOrderbookAsync(account: string, orderbook: Orderbook, - options: OrdersOptions, callback -) { +function getOrderbook(account: string, orderbook: Orderbook, + options: OrdersOptions = {} +): Promise { validate.address(account); validate.orderbook(orderbook); validate.getOrderbookOptions(options); - const getter = _.partial(getBookOffers, this.remote, account, + const getter = _.partial(getBookOffers, this.connection, account, options.ledgerVersion, options.limit); const getOffers = _.partial(getter, orderbook.base, orderbook.counter); const getReverseOffers = _.partial(getter, orderbook.counter, orderbook.base); - async.parallel([getOffers, getReverseOffers], - composeAsync((data) => formatBidsAndAsks(orderbook, _.flatten(data)), - callback)); -} - -function getOrderbook(account: string, orderbook: Orderbook, - options: OrdersOptions = {} -): Promise { - return utils.promisify(getOrderbookAsync).call(this, - account, orderbook, options); + return Promise.all([getOffers(), getReverseOffers()]).then(data => + formatBidsAndAsks(orderbook, _.flatten(data))); } module.exports = getOrderbook; diff --git a/src/api/ledger/orders.js b/src/api/ledger/orders.js index 3db36624..6b399eb2 100644 --- a/src/api/ledger/orders.js +++ b/src/api/ledger/orders.js @@ -1,47 +1,42 @@ /* @flow */ 'use strict'; const _ = require('lodash'); -const async = require('async'); const utils = require('./utils'); -const {validate, composeAsync, convertErrors} = utils.common; +const {validate} = utils.common; const parseAccountOrder = require('./parse/account-order'); -import type {Remote} from '../../core/remote'; +import type {Connection} from '../common/connection.js'; import type {OrdersOptions, Order} from './types.js'; type GetOrders = Array -function requestAccountOffers(remote: Remote, address: string, - ledgerVersion: number, marker: string, limit: number, callback -) { - remote.rawRequest({ +function requestAccountOffers(connection: Connection, address: string, + ledgerVersion: number, marker: string, limit: number +): Promise { + return connection.request({ command: 'account_offers', account: address, marker: marker, limit: utils.clamp(limit, 10, 400), ledger_index: ledgerVersion - }, - composeAsync((data) => ({ - marker: data.marker, - results: data.offers.map(_.partial(parseAccountOrder, address)) - }), convertErrors(callback))); -} - -function getOrdersAsync(account: string, options: OrdersOptions, callback) { - validate.address(account); - validate.getOrdersOptions(options); - - const getter = _.partial(requestAccountOffers, this.remote, account, - options.ledgerVersion); - utils.getRecursive(getter, options.limit, - composeAsync((orders) => _.sortBy(orders, - (order) => order.properties.sequence), callback)); + }).then(data => { + return { + marker: data.marker, + results: data.offers.map(_.partial(parseAccountOrder, address)) + }; + }); } function getOrders(account: string, options: OrdersOptions = {} ): Promise { - return utils.promisify(async.seq( - utils.getLedgerOptionsWithLedgerVersion, - getOrdersAsync)).call(this, account, options); + validate.address(account); + validate.getOrdersOptions(options); + + return utils.ensureLedgerVersion.call(this, options).then(_options => { + const getter = _.partial(requestAccountOffers, this.connection, account, + _options.ledgerVersion); + return utils.getRecursive(getter, _options.limit).then(orders => + _.sortBy(orders, (order) => order.properties.sequence)); + }); } module.exports = getOrders; diff --git a/src/api/ledger/parse/account-order.js b/src/api/ledger/parse/account-order.js index e7069806..20c777f8 100644 --- a/src/api/ledger/parse/account-order.js +++ b/src/api/ledger/parse/account-order.js @@ -1,7 +1,7 @@ /* @flow */ 'use strict'; const utils = require('./utils'); -const flags = utils.core.Remote.flags.offer; +const flags = require('./flags').orderFlags; const parseAmount = require('./amount'); const BigNumber = require('bignumber.js'); diff --git a/src/api/ledger/parse/flags.js b/src/api/ledger/parse/flags.js new file mode 100644 index 00000000..445592bc --- /dev/null +++ b/src/api/ledger/parse/flags.js @@ -0,0 +1,22 @@ +'use strict'; + +const orderFlags = { + Passive: 0x00010000, + Sell: 0x00020000 // offer was placed as a sell +}; + +const trustlineFlags = { + LowReserve: 0x00010000, // entry counts toward reserve + HighReserve: 0x00020000, + LowAuth: 0x00040000, + HighAuth: 0x00080000, + LowNoRipple: 0x00100000, + HighNoRipple: 0x00200000, + LowFreeze: 0x00400000, + HighFreeze: 0x00800000 +}; + +module.exports = { + orderFlags, + trustlineFlags +}; diff --git a/src/api/ledger/parse/orderbook-order.js b/src/api/ledger/parse/orderbook-order.js index 300d0ca1..f1499428 100644 --- a/src/api/ledger/parse/orderbook-order.js +++ b/src/api/ledger/parse/orderbook-order.js @@ -2,7 +2,7 @@ 'use strict'; const _ = require('lodash'); const utils = require('./utils'); -const flags = utils.core.Remote.flags.offer; +const flags = require('./flags').orderFlags; const parseAmount = require('./amount'); function parseOrderbookOrder(order: Object): Object { diff --git a/src/api/ledger/parse/utils.js b/src/api/ledger/parse/utils.js index b8b33263..250dec6c 100644 --- a/src/api/ledger/parse/utils.js +++ b/src/api/ledger/parse/utils.js @@ -2,8 +2,8 @@ 'use strict'; const _ = require('lodash'); const transactionParser = require('ripple-lib-transactionparser'); -const toTimestamp = require('../../../core/utils').toTimestamp; const utils = require('../utils'); +const rippleToUnixTimestamp = utils.common.rippleToUnixTimestamp; const BigNumber = require('bignumber.js'); function adjustQualityForXRP( @@ -19,7 +19,8 @@ function adjustQualityForXRP( } function parseTimestamp(tx: {date: string}): string | void { - return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined; + return tx.date ? (new Date(rippleToUnixTimestamp(tx.date))).toISOString() + : undefined; } function removeEmptyCounterparty(amount) { @@ -87,6 +88,5 @@ module.exports = { dropsToXrp: utils.common.dropsToXrp, constants: utils.common.constants, txFlags: utils.common.txFlags, - core: utils.common.core, removeUndefined: utils.common.removeUndefined }; diff --git a/src/api/ledger/pathfind.js b/src/api/ledger/pathfind.js index 06d6c881..17380bfb 100644 --- a/src/api/ledger/pathfind.js +++ b/src/api/ledger/pathfind.js @@ -1,14 +1,13 @@ /* @flow */ 'use strict'; const _ = require('lodash'); -const async = require('async'); const BigNumber = require('bignumber.js'); const utils = require('./utils'); const parsePathfind = require('./parse/pathfind'); -const {validate, composeAsync, convertErrors, toRippledAmount} = utils.common; +const {validate, toRippledAmount} = utils.common; const NotFoundError = utils.common.errors.NotFoundError; const ValidationError = utils.common.errors.ValidationError; -import type {Remote} from '../../core/remote'; +import type {Connection} from '../common/connection'; import type {RippledAmount} from '../common/types.js'; import type {GetPaths, PathFind, RippledPathsResponse, PathFindRequest} from './pathfind-types.js'; @@ -21,7 +20,7 @@ function addParams(request: PathFindRequest, result: RippledPathsResponse) { }), {destination_amount: request.destination_amount}); } -function requestPathFind(remote: Remote, pathfind: PathFind, callback) { +function requestPathFind(connection: Connection, pathfind: PathFind): Promise { const destinationAmount = _.assign({value: -1}, pathfind.destination.amount); const request: PathFindRequest = { command: 'ripple_path_find', @@ -53,8 +52,7 @@ function requestPathFind(remote: Remote, pathfind: PathFind, callback) { } } - remote.rawRequest(request, - composeAsync(_.partial(addParams, request), convertErrors(callback))); + return connection.request(request).then(paths => addParams(request, paths)); } function addDirectXrpPath(paths: RippledPathsResponse, xrpBalance: string @@ -76,16 +74,15 @@ function isRippledIOUAmount(amount: RippledAmount) { amount.currency && (amount.currency !== 'XRP'); } -function conditionallyAddDirectXRPPath(remote: Remote, address: string, - paths: RippledPathsResponse, callback -) { +function conditionallyAddDirectXRPPath(connection: Connection, address: string, + paths: RippledPathsResponse +): Promise { if (isRippledIOUAmount(paths.destination_amount) || !_.includes(paths.destination_currencies, 'XRP')) { - callback(null, paths); - } else { - utils.getXRPBalance(remote, address, undefined, - composeAsync(_.partial(addDirectXrpPath, paths), callback)); + return Promise.resolve(paths); } + return utils.getXRPBalance(connection, address, undefined).then( + xrpBalance => addDirectXrpPath(paths, xrpBalance)); } function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) { @@ -113,18 +110,13 @@ function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) { } } -function getPathsAsync(pathfind: PathFind, callback) { +function getPaths(pathfind: PathFind): Promise { validate.pathfind(pathfind); const address = pathfind.source.address; - async.waterfall([ - _.partial(requestPathFind, this.remote, pathfind), - _.partial(conditionallyAddDirectXRPPath, this.remote, address) - ], composeAsync(_.partial(formatResponse, pathfind), callback)); -} - -function getPaths(pathfind: PathFind): Promise { - return utils.promisify(getPathsAsync).call(this, pathfind); + return requestPathFind(this.connection, pathfind).then(paths => + conditionallyAddDirectXRPPath(this.connection, address, paths) + ).then(paths => formatResponse(pathfind, paths)); } module.exports = getPaths; diff --git a/src/api/ledger/settings.js b/src/api/ledger/settings.js index e71bc6d0..87705ce0 100644 --- a/src/api/ledger/settings.js +++ b/src/api/ledger/settings.js @@ -3,7 +3,7 @@ const _ = require('lodash'); const utils = require('./utils'); const parseFields = require('./parse/fields'); -const {validate, composeAsync, convertErrors} = utils.common; +const {validate} = utils.common; const AccountFlags = utils.common.constants.AccountFlags; type SettingsOptions = { @@ -48,7 +48,8 @@ function formatSettings(response) { return _.assign({}, parsedFlags, parsedFields); } -function getSettingsAsync(account: string, options: SettingsOptions, callback) { +function getSettings(account: string, options: SettingsOptions = {} +): Promise { validate.address(account); validate.getSettingsOptions(options); @@ -58,13 +59,7 @@ function getSettingsAsync(account: string, options: SettingsOptions, callback) { ledger_index: options.ledgerVersion || 'validated' }; - this.remote.rawRequest(request, - composeAsync(formatSettings, convertErrors(callback))); -} - -function getSettings(account: string, options: SettingsOptions = {} -): Promise { - return utils.promisify(getSettingsAsync).call(this, account, options); + return this.connection.request(request).then(formatSettings); } module.exports = getSettings; diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index ba8f467a..f6b7199b 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -1,28 +1,22 @@ /* @flow */ 'use strict'; const _ = require('lodash'); -const async = require('async'); const utils = require('./utils'); const parseTransaction = require('./parse/transaction'); -const {validate, convertErrors, errors} = utils.common; -const RippleError = require('../../core/rippleerror').RippleError; +const {validate, errors} = utils.common; +import type {Connection} from '../common/connection.js'; +import type {TransactionType, TransactionOptions} from './transaction-types'; -import type {Remote} from '../../core/remote'; - -import type {CallbackType, TransactionType, - GetTransactionResponseCallback, TransactionOptions} - from './transaction-types'; - -function attachTransactionDate(remote: Remote, tx: Object, - callback: CallbackType -) { +function attachTransactionDate(connection: Connection, tx: Object +): Promise { if (tx.date) { - callback(null, tx); - return; + return Promise.resolve(tx); } + if (!tx.ledger_index) { - callback(new errors.NotFoundError('ledger_index not found in tx')); - return; + return new Promise(() => { + throw new errors.NotFoundError('ledger_index not found in tx'); + }); } const request = { @@ -30,14 +24,16 @@ function attachTransactionDate(remote: Remote, tx: Object, ledger_index: tx.ledger_index }; - remote.rawRequest(request, (error, data) => { - if (error) { - callback(new errors.NotFoundError('Transaction ledger not found')); - } else if (typeof data.ledger.close_time === 'number') { - callback(null, _.assign({date: data.ledger.close_time}, tx)); - } else { - callback(new errors.ApiError('Ledger missing close_time')); + return connection.request(request).then(data => { + if (typeof data.ledger.close_time === 'number') { + return _.assign({date: data.ledger.close_time}, tx); } + throw new errors.ApiError('Ledger missing close_time'); + }).catch(error => { + if (error instanceof errors.ApiError) { + throw error; + } + throw new errors.NotFoundError('Transaction ledger not found'); }); } @@ -48,69 +44,59 @@ function isTransactionInRange(tx: Object, options: TransactionOptions) { || tx.ledger_index <= options.maxLedgerVersion); } -function getTransactionAsync(identifier: string, options: TransactionOptions, - callback: GetTransactionResponseCallback -) { - validate.identifier(identifier); - validate.getTransactionOptions(options); - - const remote = this.remote; - - function callbackWrapper(error_?: Error, tx?: Object, - maxLedgerVersion?: number - ) { - let error = error_; - - if (!error && tx && tx.validated !== true) { - return callback(new errors.NotFoundError('Transaction not found')); - } - - if (error instanceof RippleError && error.remote && - error.remote.error === 'txnNotFound') { - error = new errors.NotFoundError('Transaction not found'); - } - - // Missing complete ledger range - if (error instanceof errors.NotFoundError - && !utils.hasCompleteLedgerRange(remote, options.minLedgerVersion, - maxLedgerVersion)) { - if (utils.isPendingLedgerVersion(remote, maxLedgerVersion)) { - callback(new errors.PendingLedgerVersionError()); - } else { - callback(new errors.MissingLedgerHistoryError()); - } - // Transaction is found, but not in specified range - } else if (!error && tx && !isTransactionInRange(tx, options)) { - callback(new errors.NotFoundError('Transaction not found')); - // Transaction is not found - } else if (error) { - convertErrors(callback)(error); - } else if (!tx) { - callback(new errors.ApiError('Internal error')); - } else { - callback(error, parseTransaction(tx)); - } +function convertError(connection: Connection, options: TransactionOptions, + error: Error +): Promise { + const _error = (error.message === 'txnNotFound') ? + new errors.NotFoundError('Transaction not found') : error; + if (_error instanceof errors.NotFoundError) { + return utils.hasCompleteLedgerRange(connection, options.minLedgerVersion, + options.maxLedgerVersion).then(hasCompleteLedgerRange => { + if (!hasCompleteLedgerRange) { + return utils.isPendingLedgerVersion( + connection, options.maxLedgerVersion) + .then(isPendingLedgerVersion => { + return isPendingLedgerVersion ? + new errors.PendingLedgerVersionError() : + new errors.MissingLedgerHistoryError(); + }); + } + return _error; + }); } + return Promise.resolve(_error); +} - - function maxLedgerGetter(error_?: Error, tx?: Object) { - this.getLedgerVersion().then((version) => { - const maxLedgerVersion = options.maxLedgerVersion || version; - callbackWrapper(error_, tx, maxLedgerVersion); - }, callbackWrapper); +function formatResponse(options: TransactionOptions, tx: TransactionType +): TransactionType { + if (tx.validated !== true || !isTransactionInRange(tx, options)) { + throw new errors.NotFoundError('Transaction not found'); } - - async.waterfall([ - _.partial(remote.rawRequest.bind(remote), - {command: 'tx', transaction: identifier, binary: false}), - _.partial(attachTransactionDate, remote) - ], maxLedgerGetter.bind(this)); + return parseTransaction(tx); } function getTransaction(identifier: string, - options: TransactionOptions = {} + options: TransactionOptions = {} ): Promise { - return utils.promisify(getTransactionAsync).call(this, identifier, options); + validate.identifier(identifier); + validate.getTransactionOptions(options); + + const request = { + command: 'tx', + transaction: identifier, + binary: false + }; + + return utils.ensureLedgerVersion.call(this, options).then(_options => { + return this.connection.request(request).then(tx => + attachTransactionDate(this.connection, tx) + ).then(_.partial(formatResponse, _options)) + .catch(error => { + return convertError(this.connection, _options, error).then(_error => { + throw _error; + }); + }); + }); } module.exports = getTransaction; diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index 7bdb486e..63ee1b39 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -7,8 +7,8 @@ const {computeTransactionHash} = require('ripple-hashes'); const utils = require('./utils'); const parseTransaction = require('./parse/transaction'); const getTransaction = require('./transaction'); -const {validate, composeAsync, convertErrors} = utils.common; -import type {Remote} from '../../core/remote'; +const {validate} = utils.common; +import type {Connection} from '../common/connection.js'; import type {TransactionType} from './transaction-types'; @@ -28,8 +28,6 @@ type TransactionsOptions = { type GetTransactionsResponse = Array -type CallbackType = (err?: ?Error, data?: GetTransactionsResponse) => void - function parseBinaryTransaction(transaction) { const tx = binary.decode(transaction.tx_blob); tx.hash = computeTransactionHash(tx); @@ -103,8 +101,8 @@ function formatPartialResponse(address: string, }; } -function getAccountTx(remote: Remote, address: string, - options: TransactionsOptions, marker: string, limit: number, callback +function getAccountTx(connection: Connection, address: string, + options: TransactionsOptions, marker: string, limit: number ) { const request = { command: 'account_tx', @@ -119,13 +117,12 @@ function getAccountTx(remote: Remote, address: string, marker: marker }; - remote.rawRequest(request, - composeAsync(_.partial(formatPartialResponse, address, options), - convertErrors(callback))); + return connection.request(request).then(response => + formatPartialResponse(address, options, response)); } -function checkForLedgerGaps(remote: Remote, options: TransactionsOptions, - transactions: GetTransactionsResponse +function checkForLedgerGaps(connection: Connection, + options: TransactionsOptions, transactions: GetTransactionsResponse ) { let {minLedgerVersion, maxLedgerVersion} = options; @@ -140,54 +137,49 @@ function checkForLedgerGaps(remote: Remote, options: TransactionsOptions, } } - if (!utils.hasCompleteLedgerRange(remote, minLedgerVersion, - maxLedgerVersion)) { - throw new utils.common.errors.MissingLedgerHistoryError(); - } + return utils.hasCompleteLedgerRange(connection, minLedgerVersion, + maxLedgerVersion).then(hasCompleteLedgerRange => { + if (!hasCompleteLedgerRange) { + throw new utils.common.errors.MissingLedgerHistoryError(); + } + }); } -function formatResponse(remote: Remote, options: TransactionsOptions, +function formatResponse(connection: Connection, options: TransactionsOptions, transactions: GetTransactionsResponse ) { const compare = options.earliestFirst ? utils.compareTransactions : _.rearg(utils.compareTransactions, 1, 0); const sortedTransactions = transactions.sort(compare); - checkForLedgerGaps(remote, options, sortedTransactions); - return sortedTransactions; + return checkForLedgerGaps(connection, options, sortedTransactions).then( + () => sortedTransactions); } -function getTransactionsInternal(remote: Remote, address: string, - options: TransactionsOptions, callback -) { - const getter = _.partial(getAccountTx, remote, address, options); - const format = _.partial(formatResponse, remote, options); - utils.getRecursive(getter, options.limit, composeAsync(format, callback)); +function getTransactionsInternal(connection: Connection, address: string, + options: TransactionsOptions +): Promise { + const getter = _.partial(getAccountTx, connection, address, options); + const format = _.partial(formatResponse, connection, options); + return utils.getRecursive(getter, options.limit).then(format); } -function getTransactionsAsync(account: string, - options: TransactionsOptions, callback: CallbackType -) { +function getTransactions(account: string, options: TransactionsOptions = {} +): Promise { validate.address(account); validate.getTransactionsOptions(options); const defaults = {maxLedgerVersion: -1}; if (options.start) { - getTransaction.call(this, options.start).then(tx => { + return getTransaction.call(this, options.start).then(tx => { const ledgerVersion = tx.outcome.ledgerVersion; const bound = options.earliestFirst ? {minLedgerVersion: ledgerVersion} : {maxLedgerVersion: ledgerVersion}; const newOptions = _.assign(defaults, options, {startTx: tx}, bound); - getTransactionsInternal(this.remote, account, newOptions, callback); - }).catch(callback); - } else { - const newOptions = _.assign(defaults, options); - getTransactionsInternal(this.remote, account, newOptions, callback); + return getTransactionsInternal(this.connection, account, newOptions); + }); } -} - -function getTransactions(account: string, options: TransactionsOptions = {} -): Promise { - return utils.promisify(getTransactionsAsync).call(this, account, options); + const newOptions = _.assign(defaults, options); + return getTransactionsInternal(this.connection, account, newOptions); } module.exports = getTransactions; diff --git a/src/api/ledger/trustlines.js b/src/api/ledger/trustlines.js index 1e531764..42078077 100644 --- a/src/api/ledger/trustlines.js +++ b/src/api/ledger/trustlines.js @@ -1,12 +1,10 @@ /* @flow */ 'use strict'; const _ = require('lodash'); -const async = require('async'); const utils = require('./utils'); -const {validate, composeAsync, convertErrors} = utils.common; +const {validate} = utils.common; const parseAccountTrustline = require('./parse/account-trustline'); - -import type {Remote} from '../../core/remote'; +import type {Connection} from '../common/connection.js'; import type {TrustlinesOptions, Trustline} from './trustlines-types.js'; @@ -24,9 +22,10 @@ function formatResponse(options: TrustlinesOptions, data) { }; } -function getAccountLines(remote: Remote, address: string, ledgerVersion: number, - options: TrustlinesOptions, marker: string, limit: number, callback -) { +function getAccountLines(connection: Connection, address: string, + ledgerVersion: number, options: TrustlinesOptions, marker: string, + limit: number +): Promise { const request = { command: 'account_lines', account: address, @@ -36,27 +35,19 @@ function getAccountLines(remote: Remote, address: string, ledgerVersion: number, peer: options.counterparty }; - remote.rawRequest(request, - composeAsync(_.partial(formatResponse, options), - convertErrors(callback))); -} - -function getTrustlinesAsync(account: string, options: TrustlinesOptions, - callback: () => void -): void { - validate.address(account); - validate.getTrustlinesOptions(options); - - const getter = _.partial(getAccountLines, this.remote, account, - options.ledgerVersion, options); - utils.getRecursive(getter, options.limit, callback); + return connection.request(request).then(_.partial(formatResponse, options)); } function getTrustlines(account: string, options: TrustlinesOptions = {} ): Promise { - return utils.promisify(async.seq( - utils.getLedgerOptionsWithLedgerVersion, - getTrustlinesAsync)).call(this, account, options); + validate.address(account); + validate.getTrustlinesOptions(options); + + return this.getLedgerVersion().then(ledgerVersion => { + const getter = _.partial(getAccountLines, this.connection, account, + options.ledgerVersion || ledgerVersion, options); + return utils.getRecursive(getter, options.limit); + }); } module.exports = getTrustlines; diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index dfd0abd9..99dbd3ed 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -4,63 +4,51 @@ const _ = require('lodash'); const assert = require('assert'); const common = require('../common'); const dropsToXrp = common.dropsToXrp; -const composeAsync = common.composeAsync; -import type {Remote} from '../../core/remote'; import type {TransactionType} from './transaction-types'; import type {Issue} from '../common/types.js'; - -type Callback = (err: any, data: any) => void +import type {Connection} from '../common/connection'; type RecursiveData = { marker: string, results: Array } -type RecursiveCallback = (err: any, data: RecursiveData) => void +type Getter = (marker: ?string, limit: number) => Promise function clamp(value: number, min: number, max: number): number { assert(min <= max, 'Illegal clamp bounds'); return Math.min(Math.max(value, min), max); } -function getXRPBalance(remote: Remote, address: string, ledgerVersion?: number, - callback: Callback -): void { +function getXRPBalance(connection: Connection, address: string, + ledgerVersion?: number +): Promise { const request = { command: 'account_info', account: address, ledger_index: ledgerVersion }; - remote.rawRequest(request, - composeAsync((data) => dropsToXrp(data.account_data.Balance), callback)); + return connection.request(request).then(data => + dropsToXrp(data.account_data.Balance)); } -type Getter = (marker: ?string, limit: number, - callback: RecursiveCallback) => void - // If the marker is omitted from a response, you have reached the end // getter(marker, limit, callback), callback(error, {marker, results}) -function getRecursiveRecur(getter: Getter, marker?: string, limit: number, - callback: Callback -): void { - getter(marker, limit, (error, data) => { - if (error) { - return callback(error); - } +function getRecursiveRecur(getter: Getter, marker?: string, limit: number +): Promise { + return getter(marker, limit).then(data => { const remaining = limit - data.results.length; if (remaining > 0 && data.marker !== undefined) { - getRecursiveRecur(getter, data.marker, remaining, (_error, results) => { - return _error ? callback(_error) : - callback(null, data.results.concat(results)); - }); - } else { - return callback(null, data.results.slice(0, limit)); + return getRecursiveRecur(getter, data.marker, remaining).then(results => + data.results.concat(results) + ); } + return data.results.slice(0, limit); }); } -function getRecursive(getter: Getter, limit?: number, callback: Callback) { - getRecursiveRecur(getter, undefined, limit || Infinity, callback); +function getRecursive(getter: Getter, limit?: number): Promise { + return getRecursiveRecur(getter, undefined, limit || Infinity); } function renameCounterpartyToIssuer(amount?: Issue): ?{issuer?: string} { @@ -109,46 +97,41 @@ function compareTransactions(first: TransactionType, second: TransactionType return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1; } -function hasCompleteLedgerRange(remote: Remote, minLedgerVersion?: number, - maxLedgerVersion?: number -): boolean { - +function hasCompleteLedgerRange(connection: Connection, + minLedgerVersion?: number, maxLedgerVersion?: number +): Promise { const firstLedgerVersion = 32570; // earlier versions have been lost - return remote.getServer().hasLedgerRange( - minLedgerVersion || firstLedgerVersion, - maxLedgerVersion || remote.getLedgerSequenceSync()); + return connection.hasLedgerVersions( + minLedgerVersion || firstLedgerVersion, maxLedgerVersion); } -function isPendingLedgerVersion(remote: Remote, maxLedgerVersion: ?number -): boolean { - const currentLedger = remote.getLedgerSequenceSync(); - return currentLedger < (maxLedgerVersion || 0); +function isPendingLedgerVersion(connection: Connection, + maxLedgerVersion: ?number +): Promise { + return connection.getLedgerVersion().then(ledgerVersion => + ledgerVersion < (maxLedgerVersion || 0)); } -function getLedgerOptionsWithLedgerVersion(account: string, options: Object, - callback: (err?: ?Error, account?: string, options: Object) => void -) { +function ensureLedgerVersion(options: Object +): Promise { if (Boolean(options) && options.ledgerVersion !== undefined && options.ledgerVersion !== null ) { - callback(null, account, options); - } else { - this.getLedgerVersion().then((version) => { - callback(null, account, _.assign({}, options, {ledgerVersion: version})); - }, callback); + return Promise.resolve(options); } + return this.getLedgerVersion().then(ledgerVersion => + _.assign({}, options, {ledgerVersion})); } module.exports = { getXRPBalance, - getLedgerOptionsWithLedgerVersion, + ensureLedgerVersion, compareTransactions, renameCounterpartyToIssuer, renameCounterpartyToIssuerInOrder, getRecursive, hasCompleteLedgerRange, isPendingLedgerVersion, - promisify: common.promisify, clamp: clamp, common: common }; diff --git a/src/api/server/server.js b/src/api/server/server.js index 3e5a8a32..bf20ff49 100644 --- a/src/api/server/server.js +++ b/src/api/server/server.js @@ -4,45 +4,32 @@ const common = require('../common'); import type {GetServerInfoResponse} from '../common/serverinfo'; function isConnected(): boolean { - const server = this.remote.getServer(); - return Boolean(server && server.isConnected()); + return this.connection.isConnected(); } function getLedgerVersion(): Promise { - return common.promisify(this.remote.getLedgerSequence).call(this.remote); + return this.connection.getLedgerVersion(); } function connect(): Promise { - return common.promisify(callback => { - try { - this.remote.connect(() => callback(null)); - } catch (error) { - callback(new common.errors.RippledNetworkError(error.message)); - } - })(); + return this.connection.connect(); } function disconnect(): Promise { - return common.promisify(callback => { - try { - this.remote.disconnect(() => callback(null)); - } catch (error) { - callback(new common.errors.RippledNetworkError(error.message)); - } - })(); + return this.connection.disconnect(); } function getServerInfo(): Promise { - return common.serverInfo.getServerInfo(this.remote); + return common.serverInfo.getServerInfo(this.connection); } function getFee(): Promise { const cushion = this._feeCushion || 1.2; - return common.serverInfo.getFee(this.remote, cushion); + return common.serverInfo.getFee(this.connection, cushion); } function rippleTimeToISO8601(rippleTime: string): string { - return new Date(common.core.utils.toTimestamp(rippleTime)).toISOString(); + return new Date(common.rippleToUnixTimestamp(rippleTime)).toISOString(); } function formatLedgerClose(ledgerClose: Object): Object { diff --git a/src/api/transaction/order.js b/src/api/transaction/order.js index cd5927b6..b9243da1 100644 --- a/src/api/transaction/order.js +++ b/src/api/transaction/order.js @@ -37,18 +37,11 @@ function createOrderTransaction(account: string, order: Order): Object { return txJSON; } -function prepareOrderAsync(account: string, order: Order, - instructions: Instructions, callback -) { - const txJSON = createOrderTransaction(account, order); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function prepareOrder(account: string, order: Order, instructions: Instructions = {} ): Promise { - return utils.promisify(prepareOrderAsync.bind(this))( - account, order, instructions); + const txJSON = createOrderTransaction(account, order); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareOrder; diff --git a/src/api/transaction/ordercancellation.js b/src/api/transaction/ordercancellation.js index befc4701..3eb74bb3 100644 --- a/src/api/transaction/ordercancellation.js +++ b/src/api/transaction/ordercancellation.js @@ -17,18 +17,11 @@ function createOrderCancellationTransaction(account: string, }; } -function prepareOrderCancellationAsync(account: string, sequence: number, - instructions: Instructions, callback -) { - const txJSON = createOrderCancellationTransaction(account, sequence); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function prepareOrderCancellation(account: string, sequence: number, - instructions: Instructions = {} + instructions: Instructions = {} ): Promise { - return utils.promisify(prepareOrderCancellationAsync.bind(this))( - account, sequence, instructions); + const txJSON = createOrderCancellationTransaction(account, sequence); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareOrderCancellation; diff --git a/src/api/transaction/payment.js b/src/api/transaction/payment.js index 74aac4e2..54cc815b 100644 --- a/src/api/transaction/payment.js +++ b/src/api/transaction/payment.js @@ -141,18 +141,11 @@ function createPaymentTransaction(account: string, paymentArgument: Payment return txJSON; } -function preparePaymentAsync(account: string, payment: Payment, - instructions: Instructions, callback -) { - const txJSON = createPaymentTransaction(account, payment); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function preparePayment(account: string, payment: Payment, - instructions: Instructions = {} + instructions: Instructions ): Promise { - return utils.promisify(preparePaymentAsync.bind(this))( - account, payment, instructions); + const txJSON = createPaymentTransaction(account, payment); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = preparePayment; diff --git a/src/api/transaction/settings.js b/src/api/transaction/settings.js index 1b6603d7..341d118b 100644 --- a/src/api/transaction/settings.js +++ b/src/api/transaction/settings.js @@ -95,18 +95,11 @@ function createSettingsTransaction(account: string, settings: Settings return txJSON; } -function prepareSettingsAsync(account: string, settings: Settings, - instructions: Instructions, callback -) { - const txJSON = createSettingsTransaction(account, settings); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - -function prepareSettings(account: string, settings: Object, +function prepareSettings(account: string, settings: Settings, instructions: Instructions = {} ): Promise { - return utils.promisify(prepareSettingsAsync.bind(this))( - account, settings, instructions); + const txJSON = createSettingsTransaction(account, settings); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareSettings; diff --git a/src/api/transaction/submit.js b/src/api/transaction/submit.js index f3e80b20..d0d96081 100644 --- a/src/api/transaction/submit.js +++ b/src/api/transaction/submit.js @@ -2,9 +2,7 @@ 'use strict'; const _ = require('lodash'); const utils = require('./utils'); -const validate = utils.common.validate; -const Request = utils.common.core.Request; -const convertErrors = utils.common.convertErrors; +const {validate, convertKeysFromSnakeCaseToCamelCase} = utils.common; type Submit = { success: boolean, @@ -25,29 +23,20 @@ function isImmediateRejection(engineResult: string): boolean { return _.startsWith(engineResult, 'tem') || _.startsWith(engineResult, 'tej'); } -function convertSubmitErrors(callback) { - return function(error, data) { - if (!error && isImmediateRejection(data.engineResult)) { - callback(new utils.common.errors.RippleError('Submit failed'), data); - } else { - callback(error, data); - } - }; -} - -function submitAsync(txBlob: string, callback: (err: any, data: any) => void -): void { - validate.blob(txBlob); - const request = new Request(this.remote, 'submit'); - request.message.tx_blob = txBlob; - request.request(null, - utils.common.composeAsync( - data => utils.common.convertKeysFromSnakeCaseToCamelCase(data), - convertSubmitErrors(convertErrors(callback)))); +function formatResponse(response) { + if (isImmediateRejection(response.engine_result)) { + throw new utils.common.errors.RippledError('Submit failed'); + } + return convertKeysFromSnakeCaseToCamelCase(response); } function submit(txBlob: string): Promise { - return utils.promisify(submitAsync.bind(this))(txBlob); + validate.blob(txBlob); + const request = { + command: 'submit', + tx_blob: txBlob + }; + return this.connection.request(request).then(formatResponse); } module.exports = submit; diff --git a/src/api/transaction/suspended-payment-cancellation.js b/src/api/transaction/suspended-payment-cancellation.js index 77271306..1ae38718 100644 --- a/src/api/transaction/suspended-payment-cancellation.js +++ b/src/api/transaction/suspended-payment-cancellation.js @@ -30,19 +30,12 @@ function createSuspendedPaymentCancellationTransaction(account: string, return txJSON; } -function prepareSuspendedPaymentCancellationAsync(account: string, - payment: SuspendedPaymentCancellation, instructions: Instructions, callback -) { - const txJSON = - createSuspendedPaymentCancellationTransaction(account, payment); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function prepareSuspendedPaymentCancellation(account: string, payment: SuspendedPaymentCancellation, instructions: Instructions = {} ): Promise { - return utils.promisify(prepareSuspendedPaymentCancellationAsync) - .call(this, account, payment, instructions); + const txJSON = + createSuspendedPaymentCancellationTransaction(account, payment); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareSuspendedPaymentCancellation; diff --git a/src/api/transaction/suspended-payment-creation.js b/src/api/transaction/suspended-payment-creation.js index d2854d02..bee83e15 100644 --- a/src/api/transaction/suspended-payment-creation.js +++ b/src/api/transaction/suspended-payment-creation.js @@ -2,8 +2,7 @@ 'use strict'; const _ = require('lodash'); const utils = require('./utils'); -const validate = utils.common.validate; -const toRippledAmount = utils.common.toRippledAmount; +const {validate, unixToRippleTimestamp, toRippledAmount} = utils.common; import type {Instructions, Prepare} from './types.js'; import type {Adjustment, MaxAdjustment, Memo} from '../common/types.js'; @@ -33,10 +32,10 @@ function createSuspendedPaymentCreationTransaction(account: string, txJSON.Digest = payment.digest; } if (payment.allowCancelAfter !== undefined) { - txJSON.CancelAfter = utils.fromTimestamp(payment.allowCancelAfter); + txJSON.CancelAfter = unixToRippleTimestamp(payment.allowCancelAfter); } if (payment.allowExecuteAfter !== undefined) { - txJSON.FinishAfter = utils.fromTimestamp(payment.allowExecuteAfter); + txJSON.FinishAfter = unixToRippleTimestamp(payment.allowExecuteAfter); } if (payment.source.tag !== undefined) { txJSON.SourceTag = payment.source.tag; @@ -50,18 +49,11 @@ function createSuspendedPaymentCreationTransaction(account: string, return txJSON; } -function prepareSuspendedPaymentCreationAsync(account: string, - payment: SuspendedPaymentCreation, instructions: Instructions, callback -) { - const txJSON = createSuspendedPaymentCreationTransaction(account, payment); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function prepareSuspendedPaymentCreation(account: string, payment: SuspendedPaymentCreation, instructions: Instructions = {} ): Promise { - return utils.promisify(prepareSuspendedPaymentCreationAsync) - .call(this, account, payment, instructions); + const txJSON = createSuspendedPaymentCreationTransaction(account, payment); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareSuspendedPaymentCreation; diff --git a/src/api/transaction/suspended-payment-execution.js b/src/api/transaction/suspended-payment-execution.js index 92eda0b2..4070ad37 100644 --- a/src/api/transaction/suspended-payment-execution.js +++ b/src/api/transaction/suspended-payment-execution.js @@ -43,18 +43,11 @@ function createSuspendedPaymentExecutionTransaction(account: string, return txJSON; } -function prepareSuspendedPaymentExecutionAsync(account: string, - payment: SuspendedPaymentExecution, instructions: Instructions, callback -) { - const txJSON = createSuspendedPaymentExecutionTransaction(account, payment); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function prepareSuspendedPaymentExecution(account: string, payment: SuspendedPaymentExecution, instructions: Instructions = {} ): Promise { - return utils.promisify(prepareSuspendedPaymentExecutionAsync) - .call(this, account, payment, instructions); + const txJSON = createSuspendedPaymentExecutionTransaction(account, payment); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareSuspendedPaymentExecution; diff --git a/src/api/transaction/trustline.js b/src/api/transaction/trustline.js index 842153e0..e6058e4c 100644 --- a/src/api/transaction/trustline.js +++ b/src/api/transaction/trustline.js @@ -50,18 +50,11 @@ function createTrustlineTransaction(account: string, return txJSON; } -function prepareTrustlineAsync(account: string, - trustline: TrustLineSpecification, instructions: Instructions, callback -) { - const txJSON = createTrustlineTransaction(account, trustline); - utils.prepareTransaction(txJSON, this, instructions, callback); -} - function prepareTrustline(account: string, trustline: TrustLineSpecification, instructions: Instructions = {} ): Promise { - return utils.promisify(prepareTrustlineAsync.bind(this))( - account, trustline, instructions); + const txJSON = createTrustlineTransaction(account, trustline); + return utils.prepareTransaction(txJSON, this, instructions); } module.exports = prepareTrustline; diff --git a/src/api/transaction/utils.js b/src/api/transaction/utils.js index 6447af58..a5b33463 100644 --- a/src/api/transaction/utils.js +++ b/src/api/transaction/utils.js @@ -1,15 +1,10 @@ /* @flow */ 'use strict'; const _ = require('lodash'); -const async = require('async'); const BigNumber = require('bignumber.js'); const common = require('../common'); const txFlags = common.txFlags; -import type {Instructions} from './types.js'; - -function removeUndefined(obj: Object): Object { - return _.omit(obj, _.isUndefined); -} +import type {Instructions, Prepare} from './types.js'; function formatPrepareResponse(txJSON: Object): Object { const instructions = { @@ -31,71 +26,65 @@ function setCanonicalFlag(txJSON) { txJSON.Flags = txJSON.Flags >>> 0; } -type Callback = (err: ?(typeof Error), - data: {txJSON: string, instructions: Instructions}) => void; function prepareTransaction(txJSON: Object, api: Object, - instructions: Instructions, callback: Callback -): void { + instructions: Instructions +): Promise { common.validate.instructions(instructions); const account = txJSON.Account; setCanonicalFlag(txJSON); - function prepareMaxLedgerVersion(callback_) { + function prepareMaxLedgerVersion(): Promise { if (instructions.maxLedgerVersion !== undefined) { txJSON.LastLedgerSequence = instructions.maxLedgerVersion; - callback_(); - } else { - const offset = instructions.maxLedgerVersionOffset !== undefined ? - instructions.maxLedgerVersionOffset : 3; - api.remote.getLedgerSequence((error, ledgerVersion) => { - txJSON.LastLedgerSequence = ledgerVersion + offset; - callback_(error); - }); + return Promise.resolve(txJSON); } + const offset = instructions.maxLedgerVersionOffset !== undefined ? + instructions.maxLedgerVersionOffset : 3; + return api.connection.getLedgerVersion().then(ledgerVersion => { + txJSON.LastLedgerSequence = ledgerVersion + offset; + return txJSON; + }); } - function prepareFee(callback_) { + function prepareFee(): Promise { if (instructions.fee !== undefined) { txJSON.Fee = common.xrpToDrops(instructions.fee); - callback_(); - } else { - common.serverInfo.getFee(api.remote, api._feeCushion).then(fee => { - const feeDrops = common.xrpToDrops(fee); - if (instructions.maxFee !== undefined) { - const maxFeeDrops = common.xrpToDrops(instructions.maxFee); - txJSON.Fee = BigNumber.min(feeDrops, maxFeeDrops).toString(); - } else { - txJSON.Fee = feeDrops; - } - callback_(); - }); + return Promise.resolve(txJSON); } + const cushion = api._feeCushion; + return common.serverInfo.getFee(api.connection, cushion).then(fee => { + const feeDrops = common.xrpToDrops(fee); + if (instructions.maxFee !== undefined) { + const maxFeeDrops = common.xrpToDrops(instructions.maxFee); + txJSON.Fee = BigNumber.min(feeDrops, maxFeeDrops).toString(); + } else { + txJSON.Fee = feeDrops; + } + return txJSON; + }); } - function prepareSequence(callback_) { + function prepareSequence(): Promise { if (instructions.sequence !== undefined) { txJSON.Sequence = instructions.sequence; - callback_(null, formatPrepareResponse(txJSON)); - } else { - const request = { - command: 'account_info', - account: account - }; - api.remote.rawRequest(request, function(error, response) { - txJSON.Sequence = response.account_data.Sequence; - callback_(error, formatPrepareResponse(txJSON)); - }); + return Promise.resolve(txJSON); } + const request = { + command: 'account_info', + account: account + }; + return api.connection.request(request).then(response => { + txJSON.Sequence = response.account_data.Sequence; + return txJSON; + }); } - async.series([ - prepareMaxLedgerVersion, - prepareFee, - prepareSequence - ], common.convertErrors(function(error, results) { - callback(error, results && results[2]); - })); + return Promise.all([ + prepareMaxLedgerVersion(), + prepareFee(), + prepareSequence() + ]).then(() => formatPrepareResponse(txJSON)); } function convertStringToHex(string: string) { @@ -105,7 +94,7 @@ function convertStringToHex(string: string) { function convertMemo(memo: Object): Object { return { - Memo: removeUndefined({ + Memo: common.removeUndefined({ MemoData: convertStringToHex(memo.data), MemoType: convertStringToHex(memo.type), MemoFormat: convertStringToHex(memo.format) @@ -113,33 +102,9 @@ function convertMemo(memo: Object): Object { }; } -/** - * @param {Number} rpepoch (seconds since 1/1/2000 GMT) - * @return {Number} ms since unix epoch - * - */ -function toTimestamp(rpepoch: number): number { - return (rpepoch + 0x386D4380) * 1000; -} - -/** - * @param {Number|Date} timestamp (ms since unix epoch) - * @return {Number} seconds since ripple epoch ( 1/1/2000 GMT) - */ -function fromTimestamp(timestamp: number | Date): number { - const timestamp_ = timestamp instanceof Date ? - timestamp.getTime() : - timestamp; - return Math.round(timestamp_ / 1000) - 0x386D4380; -} - module.exports = { - removeUndefined, convertStringToHex, - fromTimestamp, - toTimestamp, convertMemo, prepareTransaction, - common, - promisify: common.promisify + common }; diff --git a/test/account-test.js b/test/account-test.js index d9849115..e32f7ad0 100644 --- a/test/account-test.js +++ b/test/account-test.js @@ -4,12 +4,6 @@ const _ = require('lodash'); const assert = require('assert'); const Account = require('ripple-lib').Account; -const addresses = require('./fixtures/addresses'); -const fixtures = require('./fixtures/api'); -const accountLinesResponse = require('./fixtures/api/rippled/account-lines'); - -const setupAPI = require('./setup-api'); - function createRemote(remoteOptions = {}) { return { @@ -58,150 +52,6 @@ function createRemote(remoteOptions = {}) { describe('Account', function() { - describe('mocked', function() { - beforeEach(setupAPI.setup); - afterEach(setupAPI.teardown); - - // this test is artificial, just to increase coverage - // because code inside listenerRemoved function in Account object - // will never be called in normal situation - // (Account object is subscribed to own events, so _subs counter never - // reach zero) - it('unsubscribe ', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - this.mockRippled.expect({ - request_subscribe: 1, - request_unsubscribe: 1, - request_account_info: 1 - }); - function dumb() {} - account.on('entry', dumb); - account._subs -= 1; - account.removeListener('entry', dumb); - setTimeout(() => { - done(); - }, 100); - }); - - it('toJson', function() { - const account = new Account(this.api.remote, addresses.ACCOUNT); - const json = account.toJson(); - assert.strictEqual(json, addresses.ACCOUNT); - }); - - it('entry', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - account.entry((error, info) => { - assert.deepEqual(info, fixtures.rippled.account_info.normal.result); - done(error); - }); - }); - - it('entry error', function(done) { - const account = new Account(this.api.remote, addresses.NOTFOUND); - account.entry((error) => { - assert(error); - error.remote.id = 0; - assert.deepEqual(error.remote, fixtures.rippled.account_info.notfound); - done(); - }); - }); - - it('getNextSequence not found', function(done) { - const account = new Account(this.api.remote, addresses.NOTFOUND); - account.getNextSequence((error, sequence) => { - assert.strictEqual(sequence, 1); - done(error); - }); - }); - - it('lines', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - account.lines((error, lines) => { - assert(lines); - const expected = JSON.parse(accountLinesResponse.normal({})) - .result.lines; - assert.deepEqual(lines.lines, expected); - done(error); - }); - }); - - it('line', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - account.line('015841551A748AD2C1F76FF6ECB0CCCD00000000', - 'rs9M85karFkCRjvc6KMWn8Coigm9cbcgcx', (error, line) => { - const expected = JSON.parse(accountLinesResponse.normal({})) - .result.lines[22]; - assert.deepEqual(line, expected); - done(error); - }); - }); - - it('line when account not found', function(done) { - const account = new Account(this.api.remote, addresses.NOTFOUND); - account.line('', 'rs9M85karFkCRjvc6KMWn8Coigm9cbcgcx', (error) => { - assert(error); - error.remote.id = 0; - assert.deepEqual(error.remote, fixtures.rippled.account_info.notfound); - done(); - }); - }); - - it('submit ', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - account._transactionManager.submit = function() { - done(); - }; - account.submit({}); - }); - - // this one just for coverage - _subs can't be zero - it('notify - no subscribers ', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - let fired = false; - account.on('transaction', function() { - fired = true; - }); - account._subs = 0; - account.notify({}); - - setTimeout(() => { - assert(!fired); - done(); - }, 100); - }); - - it('notify - transaction without account field ', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - let transactionFired; - let transactionInboundFired = false; - account.on('transaction', function(transaction) { - transactionFired = transaction; - }); - account.on('transaction-inbound', function() { - transactionInboundFired = true; - }); - account.notify({transaction: {}}); - - setTimeout(() => { - assert.deepEqual(transactionFired, {transaction: {}}); - assert(!transactionInboundFired); - done(); - }, 100); - }); - - it('notify - transaction-inbound', function(done) { - const account = new Account(this.api.remote, addresses.ACCOUNT); - account.on('transaction-inbound', function(transaction) { - assert.deepEqual(transaction, - {transaction: {Account: addresses.NOTFOUND}}); - done(); - }); - account.notify({transaction: {Account: addresses.NOTFOUND}}); - }); - - }); - describe('#_publicKeyToAddress()', function() { it('should throw an error if the key is invalid', function() { @@ -249,35 +99,34 @@ describe('Account', function() { it('should respond true if the public key corresponds to the account ' + ' address and the master key IS NOT disabled', function(done) { - const options = {Flags: 65536}; - const account = new Account(createRemote(options), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', - function(err, is_valid) { + const options = {Flags: 65536}; + const account = new Account(createRemote(options), + 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive( + '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', + function(err, is_valid) { - assert(err === null); - assert(is_valid === true); - done(); - }); + assert(err === null); + assert(is_valid === true); + done(); + }); - }); + }); it('should respond false if the public key corresponds to the account ' + ' address and the master key IS disabled', function(done) { - const account = new Account(createRemote(), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', - function(err, is_valid) { + const account = new Account(createRemote(), + 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive( + '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', + function(err, is_valid) { + assert(err === null); + assert(is_valid === false); + done(); + }); - assert(err === null); - assert(is_valid === false); - done(); - }); - - }); + }); it('should respond true if the public key corresponds to the regular key', function(done) { @@ -299,17 +148,17 @@ describe('Account', function() { it('should respond false if the public key does not correspond to an ' + ' active public key for the account', function(done) { - const account = new Account(createRemote(), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', - function(err, is_valid) { - assert(err === null); - assert(is_valid === false); - done(); - }); + const account = new Account(createRemote(), + 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); + account.publicKeyIsActive( + '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', + function(err, is_valid) { + assert(err === null); + assert(is_valid === false); + done(); + }); - }); + }); it('should respond false if the public key is invalid', function(done) { @@ -339,17 +188,17 @@ describe('Account', function() { it('should respond false if the public key does not correspond to an ' + ' active public key for the unfunded account', function(done) { - const account = new Account(createRemote(), - 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ'); - account.publicKeyIsActive( - '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', - function(err, is_valid) { - assert(err === null); - assert(is_valid === false); - done(); - }); + const account = new Account(createRemote(), + 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ'); + account.publicKeyIsActive( + '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', + function(err, is_valid) { + assert(err === null); + assert(is_valid === false); + done(); + }); - }); + }); }); diff --git a/test/api-test.js b/test/api-test.js index 2eec2cbd..12fcfd8e 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -193,10 +193,9 @@ describe('RippleAPI', function() { it('submit - failure', function() { return this.api.submit('BAD').then(() => { - assert(false, 'Should throw RippleError'); + assert(false, 'Should throw RippledError'); }).catch(error => { - assert(error instanceof this.api.errors.RippleError); - assert(error.data); + assert(error instanceof this.api.errors.RippledError); }); }); @@ -361,7 +360,7 @@ describe('RippleAPI', function() { it('getTransaction - missing ledger history', function() { const hash = hashes.NOTFOUND_TRANSACTION_HASH; // make gaps in history - this.api.remote.getServer().emit('message', ledgerClosed); + this.api.connection._ws.emit('message', JSON.stringify(ledgerClosed)); return this.api.getTransaction(hash).then(() => { assert(false, 'Should throw MissingLedgerHistoryError'); }).catch(error => { @@ -601,8 +600,8 @@ describe('RippleAPI', function() { return this.api.getServerInfo().then(() => { assert(false, 'Should throw NetworkError'); }).catch(error => { - assert(error instanceof this.api.errors.NetworkError); - assert(error.message.indexOf('too much load') !== -1); + assert(error instanceof this.api.errors.RippledError); + assert(_.includes(error.message, 'slowDown')); }); }); @@ -759,17 +758,20 @@ describe('RippleAPI', function() { assert.deepEqual(utils.renameCounterpartyToIssuer(amountArg), amountArg); }); - it('ledger utils - getRecursive', function(done) { - function getter(marker, limit, callback) { - if (marker === undefined) { - callback(null, {marker: 'A', limit: limit, results: [1]}); - } else { - callback(new Error(), null); - } + it('ledger utils - getRecursive', function() { + function getter(marker, limit) { + return new Promise((resolve, reject) => { + if (marker === undefined) { + resolve({marker: 'A', limit: limit, results: [1]}); + } else { + reject(new Error()); + } + }); } - utils.getRecursive(getter, 10, (error) => { + return utils.getRecursive(getter, 10).then(() => { + assert(false, 'Should throw Error'); + }).catch(error => { assert(error instanceof Error); - done(); }); }); @@ -852,28 +854,6 @@ describe('RippleAPI', function() { }); - describe('common utils', function() { - - it('wrapCatch', function(done) { - common.wrapCatch(function() { - throw new Error('error'); - })(function(error) { - assert(error instanceof Error); - done(); - }); - }); - - it('convertExceptions', function() { - assert.throws(common.convertExceptions(function() { - throw new Error('fall through'); - }), this.api.errors.ApiError); - assert.throws(common.convertExceptions(function() { - throw new Error('fall through'); - }), /fall through/); - }); - - }); - describe('common errors', function() { it('TransactionError', function() { @@ -909,7 +889,7 @@ describe('RippleAPI', function() { checkResult(responses.ledgerClosed, 'ledgerClosed', message); done(); }); - this.api.remote.getServer().emit('message', ledgerClosed); + this.api.connection._ws.emit('message', JSON.stringify(ledgerClosed)); }); }); @@ -965,9 +945,8 @@ describe('RippleAPI - offline', function() { }); it('RippleAPI valid options', function() { - const api = new RippleAPI({trace: true, servers: ['wss://s:1']}); - assert(api.remote.trace); - assert.deepEqual(api.remote.servers, ['wss://s:1']); + const api = new RippleAPI({servers: ['wss://s:1']}); + assert.deepEqual(api.connection._url, 'wss://s:1'); }); it('RippleAPI invalid server uri', function() { diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 0465f879..cb8d93af 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -107,8 +107,6 @@ module.exports = function(port) { assert.strictEqual(request.command, 'subscribe'); if (request.accounts) { assert(_.indexOf(_.values(addresses), request.accounts[0]) !== -1); - } else { - assert.deepEqual(request.streams, ['ledger', 'server']); } conn.send(createResponse(request, fixtures.subscribe)); }); diff --git a/test/setup-api.js b/test/setup-api.js index 8a8c8f5e..a942a74b 100644 --- a/test/setup-api.js +++ b/test/setup-api.js @@ -25,8 +25,8 @@ function setupMockRippledConnection(testcase, port, done) { testcase.mockRippled = createMockRippled(port); testcase.api = new RippleAPI({servers: ['ws://localhost:' + port]}); testcase.api.connect().then(() => { - testcase.api.remote.getServer().once('ledger_closed', () => done()); - testcase.api.remote.getServer().emit('message', ledgerClosed); + testcase.api.once('ledgerClosed', () => done()); + testcase.api.connection._ws.emit('message', JSON.stringify(ledgerClosed)); }).catch(done); }