Compare commits

...

12 Commits

Author SHA1 Message Date
Chris Clark
f9339c36bf Merge pull request #720 from clark800/update-binary-codec
0.17.1
2016-05-11 18:20:29 -07:00
Chris Clark
67dc57e9d0 Disable PhantomJS tests due to issue downloading from bitbucket 2016-05-11 17:49:57 -07:00
Chris Clark
757f3190d1 0.17.1 2016-05-11 17:49:54 -07:00
Chris Clark
0d94a15ee7 0.17.0 2016-05-05 18:35:13 -07:00
Chris Clark
7f1c80da1b Merge pull request #718 from clark800/pseudo
Add support for parsing SetFee and EnableAmendment pseudo-transactions
2016-05-05 16:15:32 -07:00
Chris Clark
f74e11bce0 Add support for parsing SetFee and EnableAmendment pseudo-transactions 2016-05-05 15:37:23 -07:00
Chris Clark
d4c843e8e3 Merge pull request #714 from darkdarkdragon/remove_tej
[FIX] remove check for `tej` class of errors.
2016-04-05 15:15:42 -07:00
Ivan Tivonenko
bae190b282 [FIX] remove check for tej class of errors. 2016-04-05 23:50:41 +03:00
Chris Clark
d2cbd70da8 Merge pull request #712 from darkdarkdragon/reconnect_fix_browser
[FIX] on reconnect wait for server to be synced
2016-03-30 15:50:58 -07:00
Ivan Tivonenko
5da78ce583 [FIX] handle websocket errors in browsers
emit not RippledNotInitializedError if server doesn't have any
completed ledgers on connect
2016-03-31 00:46:00 +03:00
Chris Clark
14bbe3e30b Merge pull request #680 from ripple/sublimator-patch-1
Fix typo in docs
2016-03-29 10:27:34 -07:00
Nicholas Dudfield
e52e2bbc68 Fix IOU Amount precision related typo in docs 2016-03-29 13:51:30 +07:00
27 changed files with 4409 additions and 400 deletions

View File

@@ -5,6 +5,7 @@ machine:
testripple.circleci.com: 127.0.0.1
dependencies:
pre:
- npm -g install npm@latest-2
- wget https://s3-us-west-2.amazonaws.com/ripple-debs/rippled_0.30.1-b11-1.deb
- sudo dpkg -i rippled_0.30.1-b11-1.deb
test:

View File

@@ -211,7 +211,7 @@ A *value* is a quantity of a currency represented as a decimal string. Be carefu
**XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). XRP has a maximum value of `100000000000` (1e11).
**Non-XRP values** have 15 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
**Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
## Amount

View File

@@ -21,7 +21,7 @@ A *value* is a quantity of a currency represented as a decimal string. Be carefu
**XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). XRP has a maximum value of `100000000000` (1e11).
**Non-XRP values** have 15 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
**Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
## Amount

4237
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.16.10",
"version": "0.17.1",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -15,7 +15,8 @@
"test": "test"
},
"dependencies": {
"ajv": "^1.4.8",
"ajv": "^4.0.5",
"ajv-i18n": "^1.2.0",
"babel-polyfill": "^6.3.14",
"babel-runtime": "^6.3.19",
"bignumber.js": "^2.0.3",
@@ -23,7 +24,7 @@
"jayson": "^1.2.2",
"lodash": "^3.1.0",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "^0.1.2",
"ripple-binary-codec": "^0.1.3",
"ripple-hashes": "^0.1.0",
"ripple-keypairs": "^0.10.0",
"ripple-lib-transactionparser": "^0.6.0",
@@ -33,7 +34,7 @@
"assert-diff": "^1.0.1",
"babel-cli": "^6.4.0",
"babel-core": "^6.4.0",
"babel-eslint": "^4.1.8",
"babel-eslint": "^6.0.4",
"babel-loader": "^6.2.1",
"babel-plugin-syntax-flow": "^6.3.13",
"babel-plugin-transform-flow-strip-types": "^6.4.0",
@@ -43,9 +44,9 @@
"coveralls": "^2.10.0",
"doctoc": "^0.15.0",
"ejs": "^2.3.4",
"eslint": "^2.1.0",
"eslint": "^2.9.0",
"eventemitter2": "^0.4.14",
"flow-bin": "^0.14",
"flow-bin": "^0.24.2",
"gulp": "^3.8.10",
"gulp-bump": "^0.1.13",
"gulp-rename": "^1.2.0",
@@ -56,7 +57,6 @@
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "^2.1.0",
"mocha-junit-reporter": "^1.9.1",
"mocha-phantomjs": "^4.0.1",
"mocha-in-sauce": "^0.0.1",
"null-loader": "^0.1.1",
"webpack": "^1.5.3",

View File

@@ -39,10 +39,10 @@ unittest() {
gulp build-min build-tests
node --harmony test-compiled/mocked-server.js > /dev/null &
echo "Running tests in PhantomJS"
mocha-phantomjs test/localrunner.html
echo "Running tests using minified version in PhantomJS"
mocha-phantomjs test/localrunnermin.html
#echo "Running tests in PhantomJS"
#mocha-phantomjs test/localrunner.html
#echo "Running tests using minified version in PhantomJS"
#mocha-phantomjs test/localrunnermin.html
echo "Running tests in SauceLabs"
http-server &
@@ -58,9 +58,9 @@ integrationtest() {
mocha test/integration/http-integration-test.js
# run integration tests in PhantomJS
gulp build-tests build-min
echo "Running integragtion tests in PhantomJS"
mocha-phantomjs test/localintegrationrunner.html
#gulp build-tests build-min
#echo "Running integragtion tests in PhantomJS"
#mocha-phantomjs test/localintegrationrunner.html
}
doctest() {

View File

@@ -40,6 +40,10 @@ function main() {
version: '43'});
sauce.browser({browserName: 'safari', platform: 'OS X 10.11',
version: '9'});
sauce.browser({browserName: 'safari', platform: 'OS X 10.10',
version: '8'});
sauce.browser({browserName: 'safari', platform: 'OS X 10.9',
version: '7'});
sauce.browser({browserName: 'chrome', platform: 'OS X 10.11',
version: '47'});
sauce.browser({browserName: 'chrome', platform: 'Linux',

View File

@@ -6,7 +6,8 @@ const WebSocket = require('ws');
const parseURL = require('url').parse;
const RangeSet = require('./rangeset').RangeSet;
const {RippledError, DisconnectedError, NotConnectedError,
TimeoutError, ResponseFormatError, ConnectionError} = require('./errors');
TimeoutError, ResponseFormatError, ConnectionError,
RippledNotInitializedError} = require('./errors');
function isStreamMessageType(type) {
return type === 'ledgerClosed' ||
@@ -39,6 +40,8 @@ class Connection extends EventEmitter {
this._nextRequestID = 1;
this._retry = 0;
this._retryTimer = null;
this._onOpenErrorBound = null;
this._onUnexpectedCloseBound = null;
}
_updateLedgerVersions(data) {
@@ -104,6 +107,8 @@ class Connection extends EventEmitter {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
}
// just in case
this._ws.removeAllListeners('open');
this._ws = null;
this._isReady = false;
if (beforeOpen) {
@@ -136,6 +141,7 @@ class Connection extends EventEmitter {
this._retry += 1;
const retryTimeout = this._calculateTimeout(this._retry);
this._retryTimer = setTimeout(() => {
this.emit('reconnecting', this._retry);
this.connect().catch(this._retryConnect.bind(this));
}, retryTimeout);
}
@@ -146,8 +152,13 @@ class Connection extends EventEmitter {
}
_onOpen() {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
if (!this._ws) {
return Promise.reject(new DisconnectedError());
}
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
}
const request = {
command: 'subscribe',
@@ -157,20 +168,22 @@ class Connection extends EventEmitter {
if (_.isEmpty(data) || !data.ledger_index) {
// rippled instance doesn't have validated ledgers
return this._disconnect(false).then(() => {
throw new NotConnectedError('Rippled not initialized');
throw new RippledNotInitializedError('Rippled not initialized');
});
}
this._updateLedgerVersions(data);
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._onUnexpectedCloseBound =
this._onUnexpectedClose.bind(this, false, null, null);
this._ws.once('close', this._onUnexpectedCloseBound);
this._rebindOnUnxpectedClose();
this._retry = 0;
this._ws.on('error', error =>
this.emit('error', 'websocket', error.message, error));
this._ws.on('error', error => {
if (process.browser && error && error.type === 'error') {
// we are in browser, ignore error - `close` event will be fired
// after error
return;
}
this.emit('error', 'websocket', error.message, error);
});
this._isReady = true;
this.emit('connected');
@@ -179,8 +192,25 @@ class Connection extends EventEmitter {
});
}
_rebindOnUnxpectedClose() {
if (this._onUnexpectedCloseBound) {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
}
this._onUnexpectedCloseBound =
this._onUnexpectedClose.bind(this, false, null, null);
this._ws.once('close', this._onUnexpectedCloseBound);
}
_unbindOnUnxpectedClose() {
if (this._onUnexpectedCloseBound) {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
}
this._onUnexpectedCloseBound = null;
}
_onOpenError(reject, error) {
this._onOpenErrorBound = null;
this._unbindOnUnxpectedClose();
reject(new NotConnectedError(error && error.message));
}
@@ -277,7 +307,10 @@ class Connection extends EventEmitter {
} else if (this._state === WebSocket.CLOSING) {
this._ws.once('close', resolve);
} else {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
if (this._onUnexpectedCloseBound) {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._onUnexpectedCloseBound = null;
}
this._ws.once('close', code => {
this._ws = null;
this._isReady = false;

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict'; // eslint-disable-line
const util = require('util');
const browserHacks = require('./browser-hacks');
@@ -53,6 +53,8 @@ class NotConnectedError extends ConnectionError {}
class DisconnectedError extends ConnectionError {}
class RippledNotInitializedError extends ConnectionError {}
class TimeoutError extends ConnectionError {}
class ResponseFormatError extends ConnectionError {}
@@ -85,6 +87,7 @@ module.exports = {
RippledError,
NotConnectedError,
DisconnectedError,
RippledNotInitializedError,
TimeoutError,
ResponseFormatError,
ValidationError,

View File

@@ -0,0 +1,9 @@
'use strict'; // eslint-disable-line strict
function parseAmendment(tx: Object) {
return {
amendment: tx.Amendment
};
}
module.exports = parseAmendment;

View File

@@ -0,0 +1,15 @@
'use strict'; // eslint-disable-line strict
const BigNumber = require('bignumber.js');
const {dropsToXrp} = require('./utils');
function parseFeeUpdate(tx: Object) {
const baseFeeDrops = (new BigNumber(tx.BaseFee, 16)).toString();
return {
baseFeeXRP: dropsToXrp(baseFeeDrops),
referenceFeeUnits: tx.ReferenceFeeUnits,
reserveBaseXRP: dropsToXrp(tx.ReserveBase),
reserveIncrementXRP: dropsToXrp(tx.ReserveIncrement)
};
}
module.exports = parseFeeUpdate;

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line strict
const assert = require('assert');
const utils = require('./utils');
const parsePayment = require('./payment');
@@ -11,6 +11,8 @@ const parseSuspendedPaymentCreation = require('./suspended-payment-creation');
const parseSuspendedPaymentExecution = require('./suspended-payment-execution');
const parseSuspendedPaymentCancellation =
require('./suspended-payment-cancellation');
const parseFeeUpdate = require('./fee-update');
const parseAmendment = require('./amendment');
function parseTransactionType(type) {
const mapping = {
@@ -23,7 +25,9 @@ function parseTransactionType(type) {
SuspendedPaymentCreate: 'suspendedPaymentCreation',
SuspendedPaymentFinish: 'suspendedPaymentExecution',
SuspendedPaymentCancel: 'suspendedPaymentCancellation',
SignerListSet: 'settings'
SignerListSet: 'settings',
SetFee: 'feeUpdate', // pseudo-transaction
EnableAmendment: 'amendment' // pseudo-transaction
};
return mapping[type] || null;
}
@@ -38,7 +42,9 @@ function parseTransaction(tx: Object): Object {
'settings': parseSettings,
'suspendedPaymentCreation': parseSuspendedPaymentCreation,
'suspendedPaymentExecution': parseSuspendedPaymentExecution,
'suspendedPaymentCancellation': parseSuspendedPaymentCancellation
'suspendedPaymentCancellation': parseSuspendedPaymentCancellation,
'feeUpdate': parseFeeUpdate,
'amendment': parseAmendment
};
const parser = mapping[type];
assert(parser !== undefined, 'Unrecognized transaction type');

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line strict
const _ = require('lodash');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
@@ -13,15 +13,18 @@ function attachTransactionDate(connection: Connection, tx: Object
return Promise.resolve(tx);
}
if (!tx.ledger_index) {
const ledgerVersion = tx.ledger_index || tx.LedgerSequence;
if (!ledgerVersion) {
return new Promise(() => {
throw new errors.NotFoundError('ledger_index not found in tx');
throw new errors.NotFoundError(
'ledger_index and LedgerSequence not found in tx');
});
}
const request = {
command: 'ledger',
ledger_index: tx.ledger_index
ledger_index: ledgerVersion
};
return connection.request(request).then(data => {

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
@@ -12,7 +12,7 @@ function isImmediateRejection(engineResult: string): boolean {
// if the required fee changes (this does not occur at the time of
// this writing, but it could change in the future)
// all other error classes can potentially result in transcation validation
return _.startsWith(engineResult, 'tem') || _.startsWith(engineResult, 'tej');
return _.startsWith(engineResult, 'tem');
}
function formatSubmitResponse(response) {

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-nested-callbacks */
'use strict'; // eslint-disable-line
'use strict'; // eslint-disable-line
const _ = require('lodash');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
@@ -708,6 +708,21 @@ describe('RippleAPI', function() {
});
});
it('getTransaction - amendment', function() {
const hash =
'A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F';
return this.api.getTransaction(hash).then(result => {
assert.deepEqual(result, responses.getTransaction.amendment);
});
});
it('getTransaction - feeUpdate', function() {
const hash =
'C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF';
return this.api.getTransaction(hash).then(result => {
assert.deepEqual(result, responses.getTransaction.feeUpdate);
});
});
});
it('getTransactions', function() {

View File

@@ -194,49 +194,81 @@ describe('Connection', function() {
}, 1);
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.timeout(7000);
const self = this;
function breakConnection() {
setTimeout(() => {
self.mockRippled.close();
setTimeout(() => {
self.mockRippled = setupAPI.createMockRippled(self._mockedServerPort);
}, 1500);
}, 21);
}
let connectsCount = 0;
let disconnectsCount = 0;
let code = 0;
this.api.connection.on('disconnected', _code => {
code = _code;
disconnectsCount += 1;
describe('reconnection test', function() {
beforeEach(function() {
this.api.connection.__workingUrl = this.api.connection._url;
this.api.connection.__doReturnBad = function() {
this._url = this.__badUrl;
const self = this;
function onReconnect(num) {
if (num >= 2) {
self._url = self.__workingUrl;
self.removeListener('reconnecting', onReconnect);
}
}
this.on('reconnecting', onReconnect);
};
});
this.api.connection.on('connected', () => {
connectsCount += 1;
if (connectsCount < 3) {
breakConnection();
}
if (connectsCount === 3) {
if (disconnectsCount !== 3) {
done(new Error('disconnectsCount must be equal to 3 (got ' +
disconnectsCount + ' instead)'));
} else if (code !== 1006) {
done(new Error('disconnect must send code 1006 (got ' + code +
' instead)'));
} else {
afterEach(function() {
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
const phantomTest = /PhantomJS/;
if (phantomTest.test(navigator.userAgent)) {
// inside PhantomJS this one just hangs, so skip as not very relevant
done();
return;
}
}
});
this.timeout(70001);
const self = this;
self.api.connection.__badUrl = 'ws://testripple.circleci.com:129';
function breakConnection() {
self.api.connection.__doReturnBad();
self.api.connection._send(JSON.stringify({
command: 'test_command',
data: {disconnectIn: 10}
}));
}
breakConnection();
let connectsCount = 0;
let disconnectsCount = 0;
let reconnectsCount = 0;
let code = 0;
this.api.connection.on('reconnecting', () => {
reconnectsCount += 1;
});
this.api.connection.on('disconnected', _code => {
code = _code;
disconnectsCount += 1;
});
const num = 3;
this.api.connection.on('connected', () => {
connectsCount += 1;
if (connectsCount < num) {
breakConnection();
}
if (connectsCount === num) {
if (disconnectsCount !== num) {
done(new Error('disconnectsCount must be equal to ' + num +
'(got ' + disconnectsCount + ' instead)'));
} else if (reconnectsCount !== num * 2) {
done(new Error('reconnectsCount must be equal to ' + num * 2 +
' (got ' + reconnectsCount + ' instead)'));
} else if (code !== 1006) {
done(new Error('disconnect must send code 1006 (got ' + code +
' instead)'));
} else {
done();
}
}
});
breakConnection();
});
});
it('should emit disconnected event with code 1000 (CLOSE_NORMAL)',
@@ -252,16 +284,17 @@ describe('Connection', function() {
it('should emit disconnected event with code 1006 (CLOSE_ABNORMAL)',
function(done
) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.api.once('error', error => {
done(new Error('should not throw error, got ' + String(error)));
});
this.api.once('disconnected', code => {
assert.strictEqual(code, 1006);
done();
});
this.mockRippled.close();
this.api.connection._send(JSON.stringify({
command: 'test_command',
data: {disconnectIn: 10}
}));
});
it('should emit connected event on after reconnect', function(done) {
@@ -380,12 +413,9 @@ describe('Connection', function() {
this.api.connection._ws.emit('message', JSON.stringify(message));
});
it('should throw NotConnectedError if server does not have validated ledgers',
it('should throw RippledNotInitializedError if server does not have ' +
'validated ledgers',
function() {
if (process.browser) {
// do not work in browser now, skipping
return false;
}
this.timeout(3000);
this.api.connection._send(JSON.stringify({
@@ -397,18 +427,13 @@ describe('Connection', function() {
return api.connect().then(() => {
assert(false, 'Must have thrown!');
}, error => {
assert(error instanceof this.api.errors.NotConnectedError,
'Must throw NotConnectedError, got instead ' + String(error));
assert(error instanceof this.api.errors.RippledNotInitializedError,
'Must throw RippledNotInitializedError, got instead ' + String(error));
});
});
it('should try to reconnect on empty subscribe response on reconnect',
function(done) {
if (process.browser) {
// do not work in browser now, skipping
done();
return;
}
this.timeout(23000);
this.api.on('error', error => {

View File

@@ -0,0 +1,17 @@
{
"type": "amendment",
"address": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"sequence": 0,
"id": "A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F",
"specification": {
"amendment": "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE"
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2016-05-05T16:33:30.000Z",
"fee": "0",
"balanceChanges": {},
"orderbookChanges": {},
"indexInLedger": 0
}
}

View File

@@ -0,0 +1,21 @@
{
"type": "feeUpdate",
"address": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"sequence": 0,
"id": "C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF",
"specification": {
"baseFeeXRP": "0.00001",
"referenceFeeUnits": 10,
"reserveBaseXRP": "50",
"reserveIncrementXRP": "12.5"
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2014-08-08T16:57:50.000Z",
"fee": "0",
"balanceChanges": {},
"orderbookChanges": {},
"ledgerVersion": 3717633,
"indexInLedger": 3
}
}

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict'; // eslint-disable-line strict
module.exports = {
generateAddress: require('./generate-address.json'),
@@ -43,7 +43,9 @@ module.exports = {
suspendedPaymentExecution:
require('./get-transaction-suspended-payment-execution.json'),
suspendedPaymentExecutionSimple:
require('./get-transaction-suspended-payment-execution-simple.json')
require('./get-transaction-suspended-payment-execution-simple.json'),
amendment: require('./get-transaction-amendment.json'),
feeUpdate: require('./get-transaction-fee-update.json')
},
getTransactions: {
normal: require('./get-transactions.json'),

View File

@@ -33,6 +33,7 @@ module.exports = {
server_info: {
normal: require('./server-info'),
noValidated: require('./server-info-no-validated'),
syncing: require('./server-info-syncing'),
error: require('./server-info-error')
},
path_find: {
@@ -71,6 +72,8 @@ module.exports = {
require('./tx/suspended-payment-execution-simple.json'),
Unrecognized: require('./tx/unrecognized.json'),
NoMeta: require('./tx/no-meta.json'),
LedgerZero: require('./tx/ledger-zero.json')
LedgerZero: require('./tx/ledger-zero.json'),
Amendment: require('./tx/amendment.json'),
SetFee: require('./tx/set-fee.json')
}
};

View File

@@ -0,0 +1,30 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"info": {
"build_version": "0.24.0-rc1",
"complete_ledgers": "32570-6595042",
"hostid": "ARTS",
"io_latency_ms": 1,
"last_close": {
"converge_time_s": 2.007,
"proposers": 4
},
"load_factor": 1,
"peers": 53,
"pubkey_node": "n94wWvFUmaKGYrKUGgpv1DyYgDeXRGdACkNQaSe7zJiy5Znio7UC",
"server_state": "syncing",
"validated_ledger": {
"age": 5,
"base_fee_xrp": 0.00001,
"hash": "4482DEE5362332F54A4036ED57EE1767C9F33CF7CE5A6670355C16CECE381D46",
"reserve_base_xrp": 20,
"reserve_inc_xrp": 5,
"seq": 6595042
},
"validation_quorum": 3
}
}
}

40
test/fixtures/rippled/tx/amendment.json vendored Normal file
View File

@@ -0,0 +1,40 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"Amendment": "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE",
"Fee": "0",
"Flags": 65536,
"date": 515781210,
"LedgerSequence": 20889601,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "EnableAmendment",
"hash": "A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F",
"meta": {
"AffectedNodes": [
{
"CreatedNode": {
"LedgerEntryType": "Amendments",
"LedgerIndex": "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4",
"NewFields": {
"Majorities": [
{
"Majority": {
"Amendment": "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE",
"CloseTime": 515781202
}
}
]
}
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}

43
test/fixtures/rippled/tx/set-fee.json vendored Normal file
View File

@@ -0,0 +1,43 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"hash": "C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF",
"ledger_index": 3717633,
"date": 460832270,
"TransactionType": "SetFee",
"Sequence": 0,
"ReferenceFeeUnits": 10,
"ReserveBase": 50000000,
"ReserveIncrement": 12500000,
"BaseFee": "000000000000000A",
"Fee": "0",
"SigningPubKey": "",
"Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"meta": {
"TransactionIndex": 3,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "FeeSettings",
"LedgerIndex": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651",
"PreviousFields": {
"ReserveBase": 20000000,
"ReserveIncrement": 5000000
},
"FinalFields": {
"Flags": 0,
"ReferenceFeeUnits": 10,
"ReserveBase": 50000000,
"ReserveIncrement": 12500000,
"BaseFee": "000000000000000A"
}
}
}
],
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}

View File

@@ -1,4 +1,4 @@
'use strict'; // eslint-disable-line
'use strict'; // eslint-disable-line
const _ = require('lodash');
const assert = require('assert');
const WebSocketServer = require('ws').Server;
@@ -9,6 +9,7 @@ const hashes = require('./fixtures/hashes');
const transactionsResponse = require('./fixtures/rippled/account-tx');
const accountLinesResponse = require('./fixtures/rippled/account-lines');
const fullLedger = require('./fixtures/rippled/ledger-full-38129.json');
const {getFreePort} = require('./utils/net-utils');
function isUSD(json) {
return json === 'USD' || json === '0000000000000000000000005553440000000000';
@@ -46,7 +47,7 @@ function createLedgerResponse(request, response) {
return JSON.stringify(newResponse);
}
module.exports = function(port) {
module.exports = function createMockRippled(port) {
const mock = new WebSocketServer({port: port});
_.assign(mock, EventEmitter2.prototype);
@@ -71,6 +72,11 @@ module.exports = function(port) {
};
mock.on('connection', function(conn) {
if (mock.config.breakNextConnection) {
mock.config.breakNextConnection = false;
conn.terminate();
return;
}
this.socket = conn;
conn.config = {};
conn.on('message', function(requestJSON) {
@@ -107,6 +113,22 @@ module.exports = function(port) {
assert.strictEqual(request.command, 'test_command');
if (request.data.disconnectIn) {
setTimeout(conn.terminate.bind(conn), request.data.disconnectIn);
} else if (request.data.openOnOtherPort) {
getFreePort().then(newPort => {
createMockRippled(newPort);
conn.send(createResponse(request, {status: 'success', type: 'response',
result: {port: newPort}}
));
});
} else if (request.data.closeServerAndReopen) {
setTimeout(() => {
conn.terminate();
close.call(mock, () => {
setTimeout(() => {
createMockRippled(port);
}, request.data.closeServerAndReopen);
});
}, 10);
}
});
@@ -128,6 +150,9 @@ module.exports = function(port) {
conn.close();
} else if (conn.config.serverInfoWithoutValidated) {
conn.send(createResponse(request, fixtures.server_info.noValidated));
} else if (mock.config.returnSyncingServerInfo) {
mock.config.returnSyncingServerInfo--;
conn.send(createResponse(request, fixtures.server_info.syncing));
} else {
conn.send(createResponse(request, fixtures.server_info.normal));
}
@@ -267,6 +292,12 @@ module.exports = function(port) {
} else if (request.transaction ===
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA13') {
conn.send(createResponse(request, fixtures.tx.LedgerZero));
} else if (request.transaction ===
'A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F') {
conn.send(createResponse(request, fixtures.tx.Amendment));
} else if (request.transaction ===
'C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF') {
conn.send(createResponse(request, fixtures.tx.SetFee));
} else {
assert(false, 'Unrecognized transaction hash: ' + request.transaction);
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-nested-callbacks */
'use strict';
'use strict'; // eslint-disable-line
const {RippleAPI, RippleAPIBroadcast} = require('ripple-api');
const ledgerClosed = require('./fixtures/rippled/ledger-close');
@@ -8,12 +8,22 @@ const port = 34371;
const baseUrl = 'ws://testripple.circleci.com:';
function setup(port_ = port) {
return new Promise((resolve, reject) => {
this.api = new RippleAPI({server: baseUrl + port_});
this.api.connect().then(() => {
this.api.once('ledger', () => resolve());
this.api.connection._ws.emit('message', JSON.stringify(ledgerClosed));
}).catch(reject);
const tapi = new RippleAPI({server: baseUrl + port_});
return tapi.connect().then(() => {
return tapi.connection.request({
command: 'test_command',
data: {openOnOtherPort: true}
});
}).then(got => {
return new Promise((resolve, reject) => {
this.api = new RippleAPI({server: baseUrl + got.port});
this.api.connect().then(() => {
this.api.once('ledger', () => resolve());
this.api.connection._ws.emit('message', JSON.stringify(ledgerClosed));
}).catch(reject);
});
}).then(() => {
return tapi.disconnect();
});
}
@@ -33,6 +43,7 @@ function teardown() {
if (this.api.isConnected()) {
return this.api.disconnect();
}
return undefined;
}
module.exports = {

View File

@@ -1,29 +1,11 @@
'use strict'; // eslint-disable-line
const net = require('net');
const RippleAPI = require('ripple-api').RippleAPI;
const RippleAPIBroadcast = require('ripple-api').RippleAPIBroadcast;
const ledgerClosed = require('./fixtures/rippled/ledger-close');
const createMockRippled = require('./mock-rippled');
const {getFreePort} = require('./utils/net-utils');
// using a free port instead of a constant port enables parallelization
function getFreePort() {
return new Promise((resolve, reject) => {
const server = net.createServer();
let port;
server.on('listening', function() {
port = server.address().port;
server.close();
});
server.on('close', function() {
resolve(port);
});
server.on('error', function(error) {
reject(error);
});
server.listen(0);
});
}
function setupMockRippledConnection(testcase, port) {
return new Promise((resolve, reject) => {

26
test/utils/net-utils.js Normal file
View File

@@ -0,0 +1,26 @@
'use strict'; // eslint-disable-line
const net = require('net');
// using a free port instead of a constant port enables parallelization
function getFreePort() {
return new Promise((resolve, reject) => {
const server = net.createServer();
let port;
server.on('listening', function() {
port = server.address().port;
server.close();
});
server.on('close', function() {
resolve(port);
});
server.on('error', function(error) {
reject(error);
});
server.listen(0);
});
}
module.exports = {
getFreePort
};