diff --git a/src/js/ripple/server.js b/src/js/ripple/server.js index 6ce8cb17..500e27cb 100644 --- a/src/js/ripple/server.js +++ b/src/js/ripple/server.js @@ -85,18 +85,20 @@ function Server(remote, opts) { this._url = this._opts.url = (this._opts.secure ? 'wss://' : 'ws://') + this._opts.host + ':' + this._opts.port; - this.on('message', onMessage); + this._hostid = ''; function onMessage(message) { self._handleMessage(message); }; - this.on('response_subscribe', onSubscribeResponse); + this.on('message', onMessage); function onSubscribeResponse(message) { self._handleResponseSubscribe(message); }; + this.on('response_subscribe', onSubscribeResponse); + function setActivityInterval() { var interval = self._checkActivity.bind(self); self._activityInterval = setInterval(interval, 1000); @@ -120,6 +122,16 @@ function Server(remote, opts) { this.on('load_changed', function(load) { self._updateScore('loadchange', load); }); + + this.once('response_server_info', function(message) { + if (message.info.hostid) { + self._hostid = message.info.hostid; + } + }); + + this.once('connect', function() { + self._request(self._remote.requestServerInfo()); + }); }; util.inherits(Server, EventEmitter); @@ -168,7 +180,7 @@ Server.websocketConstructor = function() { Server.prototype._setState = function(state) { if (state !== this._state) { if (this._remote.trace) { - log.info('set_state:', state); + log.info('set_state:', this._hostid, state); } this._state = state; @@ -258,10 +270,12 @@ Server.prototype._updateScore = function(type, data) { }; /** - * Get the remote address for a server. + * Get the server's remote address + * * Incompatible with ripple-lib client build */ +Server.prototype.getRemoteAddress = Server.prototype._remoteAddress = function() { var address; try { @@ -271,6 +285,14 @@ Server.prototype._remoteAddress = function() { return address; }; +/** + * Get the server's hostid + */ + +Server.prototype.getHostID = function() { + return this._hostid; +}; + /** * Disconnect from rippled WebSocket server * @@ -324,7 +346,7 @@ Server.prototype.connect = function() { } if (this._remote.trace) { - log.info('connect:', this._opts.url); + log.info('connect:', this._hostid, this._opts.url); } // Ensure any existing socket is given the command to close first. @@ -361,7 +383,7 @@ Server.prototype.connect = function() { self.emit('socket_error'); if (self._remote.trace) { - log.info('onerror:', self._opts.url, e.data || e); + log.info('onerror:', self._hostid, self._opts.url, e.data || e); } // Most connection errors for WebSockets are conveyed as 'close' events with @@ -385,7 +407,7 @@ Server.prototype.connect = function() { ws.onclose = function onClose() { if (ws === self._ws) { if (self._remote.trace) { - log.info('onclose:', self._opts.url, ws.readyState); + log.info('onclose:', self._hostid, self._opts.url, ws.readyState); } self._handleClose(); } @@ -418,7 +440,7 @@ Server.prototype._retryConnect = function() { function connectionRetry() { if (self._shouldConnect) { if (self._remote.trace) { - log.info('retry', self._opts.url); + log.info('retry', self._hostid, self._opts.url); } self.connect(); } @@ -524,14 +546,14 @@ Server.prototype._handleResponse = function(message) { if (!request) { if (this._remote.trace) { - log.info('UNEXPECTED:', this._opts.url, message); + log.info('UNEXPECTED:', this._hostid, this._opts.url, message); } return; } if (message.status === 'success') { if (this._remote.trace) { - log.info('response:', this._opts.url, message); + log.info('response:', this._hostid, this._opts.url, message); } var command = request.message.command; @@ -545,7 +567,7 @@ Server.prototype._handleResponse = function(message) { }); } else if (message.error) { if (this._remote.trace) { - log.info('error:', this._opts.url, message); + log.info('error:', this._hostid, this._opts.url, message); } var error = { @@ -560,7 +582,7 @@ Server.prototype._handleResponse = function(message) { Server.prototype._handlePathFind = function(message) { if (this._remote.trace) { - log.info('path_find:', this._opts.url, message); + log.info('path_find:', this._hostid, this._opts.url, message); } }; @@ -618,7 +640,7 @@ Server.isLoadStatus = function(message) { Server.prototype._sendMessage = function(message) { if (this._ws) { if (this._remote.trace) { - log.info('request:', this._opts.url, message); + log.info('request:', this._hostid, this._opts.url, message); } this._ws.send(JSON.stringify(message)); } @@ -640,7 +662,7 @@ Server.prototype._request = function(request) { // Only bother if we are still connected. if (!this._ws) { if (this._remote.trace) { - log.info('request: DROPPING:', self._opts.url, request.message); + log.info('request: DROPPING:', self._hostid, self._opts.url, request.message); } return; } @@ -658,20 +680,17 @@ Server.prototype._request = function(request) { self._sendMessage(request.message); }; - if (this._isConnected(request)) { + var isSubscribeRequest = request && request.message.command === 'subscribe' && this._ws.readyState === 1; + + if (this._isConnected() || isSubscribeRequest) { sendRequest(); } else { - // XXX There are many ways to make this smarter. this.once('connect', sendRequest); } }; -Server.prototype._isConnected = function(request) { - var isSubscribeRequest = request - && request.message.command === 'subscribe' - && this._ws.readyState === 1; - - return this._connected || (this._ws && isSubscribeRequest); +Server.prototype._isConnected = function() { + return this._connected; }; /** diff --git a/test/account-test.js b/test/account-test.js index 78e4fb48..06f8a5ce 100644 --- a/test/account-test.js +++ b/test/account-test.js @@ -3,7 +3,7 @@ var Account = require('../src/js/ripple/account').Account; describe('Account', function(){ - describe('._publicKeyToAddress()', function(){ + describe('#_publicKeyToAddress()', function(){ it('should throw an error if the key is invalid', function(){ try { @@ -29,7 +29,7 @@ describe('Account', function(){ }); - describe('.publicKeyIsActive()', function(){ + describe('#publicKeyIsActive()', function(){ it('should respond true if the public key corresponds to the account address and the master key IS NOT disabled', function(){ @@ -178,4 +178,4 @@ describe('Account', function(){ }); -}); \ No newline at end of file +}); diff --git a/test/remote-test.js b/test/remote-test.js index 5c29d6ec..d2d92a0f 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -33,217 +33,160 @@ describe('Remote', function () { }; }) - describe('remote server initialization - url object', function() { - it('should construct url', function (done) { + it('remote server initialization - url object', function() { + var remote = new Remote({ + servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ], + }); + assert(Array.isArray(remote._servers)); + assert(remote._servers[0] instanceof Server); + assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); + }) + + it('remote server initialization - url object - no secure property', function() { + var remote = new Remote({ + servers: [ { host: 's-west.ripple.com', port: 443 } ] + }); + assert(Array.isArray(remote._servers)); + assert(remote._servers[0] instanceof Server); + assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); + }) + + it('remote server initialization - url object - secure: false', function() { + var remote = new Remote({ + servers: [ { host: 's-west.ripple.com', port: 443, secure: false } ] + }); + assert(Array.isArray(remote._servers)); + assert(remote._servers[0] instanceof Server); + assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443'); + }); + + it('remote server initialization - url object - string port', function() { + var remote = new Remote({ + servers: [ { host: 's-west.ripple.com', port: '443', secure: true } ] + }); + assert(Array.isArray(remote._servers)); + assert(remote._servers[0] instanceof Server); + assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); + }) + + it('remote server initialization - url object - invalid host', function() { + assert.throws( + function() { var remote = new Remote({ - servers: [ { host: 's-west.ripple.com', port: 443, secure: true } ], + servers: [ { host: '+', port: 443, secure: true } ] }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - done(); - }) - }); + }, Error); + }) - describe('remote server initialization - url object - no secure property', function() { - it('should construct url', function (done) { + it('remote server initialization - url object - invalid port', function() { + assert.throws( + function() { var remote = new Remote({ - servers: [ { host: 's-west.ripple.com', port: 443 } ] + servers: [ { host: 's-west.ripple.com', port: null, secure: true } ] }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - done(); - }) + }, TypeError); }); - describe('remote server initialization - url object - secure: false', function() { - it('should construct url', function (done) { + it('remote server initialization - url object - port out of range', function() { + assert.throws( + function() { var remote = new Remote({ - servers: [ { host: 's-west.ripple.com', port: 443, secure: false } ] + servers: [ { host: 's-west.ripple.com', port: 65537, secure: true } ] }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443'); - done(); - }) + }, Error); }); - describe('remote server initialization - url object - string port', function() { - it('should construct url', function (done) { + it('remote server initialization - url string', function() { + var remote = new Remote({ + servers: [ 'wss://s-west.ripple.com:443' ] + }); + assert(Array.isArray(remote._servers)); + assert(remote._servers[0] instanceof Server); + assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); + }); + + it('remote server initialization - url string - ws://', function() { + var remote = new Remote({ + servers: [ 'ws://s-west.ripple.com:443' ] + }); + assert(Array.isArray(remote._servers)); + assert(remote._servers[0] instanceof Server); + assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443'); + }); + + it('remote server initialization - url string - invalid host', function() { + assert.throws( + function() { var remote = new Remote({ - servers: [ { host: 's-west.ripple.com', port: '443', secure: true } ] + servers: [ 'ws://+:443' ] }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - done(); - }) + }, Error + ); }); - describe('remote server initialization - url object - invalid host', function() { - it('should construct url', function (done) { - assert.throws( - function() { - var remote = new Remote({ - servers: [ { host: '+', port: 443, secure: true } ] - }); - }, Error); - done(); - }) - }); - - describe('remote server initialization - url object - invalid port', function() { - it('should construct url', function (done) { - assert.throws( - function() { - var remote = new Remote({ - servers: [ { host: 's-west.ripple.com', port: null, secure: true } ] - }); - }, TypeError); - done(); - }) - }); - - describe('remote server initialization - url object - port out of range', function() { - it('should construct url', function (done) { - assert.throws( - function() { - var remote = new Remote({ - servers: [ { host: 's-west.ripple.com', port: 65537, secure: true } ] - }); - }, Error); - done(); - }) - }); - - describe('remote server initialization - url string', function() { - it('should construct url', function (done) { + it('remote server initialization - url string - invalid port', function() { + assert.throws( + function() { var remote = new Remote({ - servers: [ 'wss://s-west.ripple.com:443' ] + servers: [ 'ws://s-west.ripple.com:null' ] }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - done(); - }) + }, Error + ); }); - describe('remote server initialization - url string - ws://', function() { - it('should construct url', function (done) { + it('remote server initialization - url string - port out of range', function() { + assert.throws( + function() { var remote = new Remote({ - servers: [ 'ws://s-west.ripple.com:443' ] + servers: [ 'ws://s-west.ripple.com:65537:' ] }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443'); - done(); - }) + }, Error + ); }); - describe('remote server initialization - url string - invalid host', function() { - it('should construct url', function (done) { - assert.throws( - function() { - var remote = new Remote({ - servers: [ 'ws://+:443' ] - }); - }, Error - ); - done(); - }) - }); - - describe('remote server initialization - url string - invalid port', function() { - it('should construct url', function (done) { - assert.throws( - function() { - var remote = new Remote({ - servers: [ 'ws://s-west.ripple.com:null' ] - }); - }, Error - ); - done(); - }) - }); - - describe('remote server initialization - url string - port out of range', function() { - it('should construct url', function (done) { - assert.throws( - function() { - var remote = new Remote({ - servers: [ 'ws://s-west.ripple.com:65537:' ] - }); - }, Error - ); - done(); - }) - }); - - describe('request constructors', function () { + it('request constructors', function () { beforeEach(function () { callback = function () {} remote = new Remote(options); }); - describe('requesting a ledger', function () { - it('should return a request', function (done) { - var request = remote.request_ledger(null, {}, callback); - assert(request instanceof Request); - done(); - }) + it('requesting a ledger', function () { + var request = remote.request_ledger(null, {}, callback); + assert(request instanceof Request); }); - describe('requesting server info', function () { - it('should return a request object', function (done) { - var request = remote.request_server_info(null, {}, callback); - assert(request instanceof Request); - done(); - }) + it('requesting server info', function () { + var request = remote.request_server_info(null, {}, callback); + assert(request instanceof Request); }) - describe('requesting peers', function () { - it('should return a request object', function (done) { - var request = remote.request_peers(null, {}, callback); - assert(request instanceof Request); - done(); - }); + it('requesting peers', function () { + var request = remote.request_peers(null, {}, callback); + assert(request instanceof Request); }); - describe('requesting a connection', function () { - it('should return a request object', function (done) { - var request = remote.request_connect(null, {}, callback); - assert(request instanceof Request); - done(); - }); + it('requesting a connection', function () { + var request = remote.request_connect(null, {}, callback); + assert(request instanceof Request); }); - describe('making a unique node list add request', function () { - it('should return a request object', function (done) { - var request = remote.request_unl_add(null, {}, callback); - assert(request instanceof Request); - done(); - }); + it('making a unique node list add request', function () { + var request = remote.request_unl_add(null, {}, callback); + assert(request instanceof Request); }); - describe('making a unique node list request', function () { - it('should return a request object', function (done) { - var request = remote.request_unl_list(null, {}, callback); - assert(request instanceof Request); - done(); - }); + it('making a unique node list request', function () { + var request = remote.request_unl_list(null, {}, callback); + assert(request instanceof Request); }); - describe('making a unique node list delete request', function () { - it('should return a request object', function (done) { - var request = remote.request_unl_delete(null, {}, callback); - assert(request instanceof Request); - done(); - }); + it('making a unique node list delete request', function () { + var request = remote.request_unl_delete(null, {}, callback); + assert(request instanceof Request); }); }) - describe('create remote and get pending transactions', function() { + it('create remote and get pending transactions', function() { before(function() { tx = [{ tx_json: { diff --git a/test/server-test.js b/test/server-test.js index f0607f8e..f5a89369 100644 --- a/test/server-test.js +++ b/test/server-test.js @@ -84,16 +84,16 @@ describe('Server', function() { var server = new Server(new Remote(), 'wss://localhost:5006'); var ledger = { - 'type': 'ledgerClosed', - 'fee_base': 10, - 'fee_ref': 10, - 'ledger_hash': 'D29E1F2A2617A88E9DAA14F468B169E6875092ECA0B3B1FA2BE1BC5524DE7CB2', - 'ledger_index': 7035609, - 'ledger_time': 455327690, - 'reserve_base': 20000000, - 'reserve_inc': 5000000, - 'txn_count': 1, - 'validated_ledgers': '32570-7035609' + type: 'ledgerClosed', + fee_base: 10, + fee_ref: 10, + ledger_hash: 'D29E1F2A2617A88E9DAA14F468B169E6875092ECA0B3B1FA2BE1BC5524DE7CB2', + ledger_index: 7035609, + ledger_time: 455327690, + reserve_base: 20000000, + reserve_inc: 5000000, + txn_count: 1, + validated_ledgers: '32570-7035609' }; server._updateScore = function(type, data) { @@ -125,8 +125,8 @@ describe('Server', function() { var server = new Server(new Remote(), 'wss://localhost:5006'); var load = { - 'fee_base': 10, - 'fee_ref': 10 + fee_base: 10, + fee_ref: 10 }; server._updateScore = function(type, data) { @@ -361,22 +361,22 @@ describe('Server', function() { }); ws.send(JSON.stringify({ - 'id': 0, - 'status': 'success', - 'type': 'response', - 'result': { - 'fee_base': 10, - 'fee_ref': 10, - 'ledger_hash': '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - 'ledger_index': 7053695, - 'ledger_time': 455414390, - 'load_base': 256, - 'load_factor': 256, - 'random': 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - 'reserve_base': 20000000, - 'reserve_inc': 5000000, - 'server_status': 'full', - 'validated_ledgers': '32570-7053695' + id: 0, + status: 'success', + type: 'response', + result: { + fee_base: 10, + fee_ref: 10, + ledger_hash: '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', + ledger_index: 7053695, + ledger_time: 455414390, + load_base: 256, + load_factor: 256, + random: 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', + reserve_base: 20000000, + reserve_inc: 5000000, + server_status: 'full', + validated_ledgers: '32570-7053695' } })); @@ -512,22 +512,22 @@ describe('Server', function() { }); ws.send(JSON.stringify({ - 'id': 0, - 'status': 'success', - 'type': 'response', - 'result': { - 'fee_base': 10, - 'fee_ref': 10, - 'ledger_hash': '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - 'ledger_index': 7053695, - 'ledger_time': 455414390, - 'load_base': 256, - 'load_factor': 256, - 'random': 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - 'reserve_base': 20000000, - 'reserve_inc': 5000000, - 'server_status': 'full', - 'validated_ledgers': '32570-7053695' + id: 0, + status: 'success', + type: 'response', + result: { + fee_base: 10, + fee_ref: 10, + ledger_hash: '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', + ledger_index: 7053695, + ledger_time: 455414390, + load_base: 256, + load_factor: 256, + random: 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', + reserve_base: 20000000, + reserve_inc: 5000000, + server_status: 'full', + validated_ledgers: '32570-7053695' } })); }); @@ -642,7 +642,29 @@ describe('Server', function() { type: 'response', status: 'success', result: { - test: 'property' + info: { + build_version: "0.25.2-rc1", + complete_ledgers: "32570-7623483", + hostid: "MAC", + io_latency_ms: 1, + last_close: { + converge_time_s: 2.052, + proposers: 5 + }, + load_factor: 1, + peers: 50, + pubkey_node: "n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW", + server_state: "full", + validated_ledger: { + age: 5, + base_fee_xrp: 0.00001, + hash: "AB575193C623179078BE7CC42965FD4262EE8611D1CE7F839CEEBFFEF4B653B6", + reserve_base_xrp: 20, + reserve_inc_xrp: 5, + seq: 7623483 + }, + validation_quorum: 3 + } } }; @@ -841,8 +863,6 @@ describe('Server', function() { server._ws = { readyState: 1 }; assert(!server._isConnected()); - assert(!server._isConnected({ message: { command: 'ping' } })); - assert(server._isConnected({ message: { command: 'subscribe' } })); server._connected = true; @@ -885,4 +905,102 @@ describe('Server', function() { server._reserve_inc = 5000000; assert.strictEqual(server._reserve().to_json(), '20000000'); }); + + it('Cache hostid', function(done) { + var wss = new ws.Server({ port: 5748 }); + + wss.once('connection', function(ws) { + function sendServerInfo(message) { + ws.send(JSON.stringify({ + id: message.id, + status: 'success', + type: 'response', + result: { + info: { + build_version: "0.25.2-rc1", + complete_ledgers: "32570-7623483", + hostid: "MAC", + io_latency_ms: 1, + last_close: { + converge_time_s: 2.052, + proposers: 5 + }, + load_factor: 1, + peers: 50, + pubkey_node: "n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW", + server_state: "full", + validated_ledger: { + age: 5, + base_fee_xrp: 0.00001, + hash: "AB575193C623179078BE7CC42965FD4262EE8611D1CE7F839CEEBFFEF4B653B6", + reserve_base_xrp: 20, + reserve_inc_xrp: 5, + seq: 7623483 + }, + validation_quorum: 3 + } + } + })); + }; + + function sendSubscribe(message) { + ws.send(JSON.stringify({ + id: message.id, + status: 'success', + type: 'response', + result: { + fee_base: 10, + fee_ref: 10, + ledger_hash: '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', + ledger_index: 7053695, + ledger_time: 455414390, + load_base: 256, + load_factor: 256, + random: 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', + reserve_base: 20000000, + reserve_inc: 5000000, + server_status: 'full', + validated_ledgers: '32570-7053695' + } + })); + }; + + ws.on('message', function(message) { + var m = JSON.parse(message); + + switch (m.command) { + case 'subscribe': + assert.strictEqual(m.command, 'subscribe'); + assert.deepEqual(m.streams, [ 'ledger', 'server' ]); + sendSubscribe(m); + break; + case 'server_info': + assert.strictEqual(m.command, 'server_info'); + sendServerInfo(m); + wss.close(); + break; + } + }); + }); + + var server = new Server(new Remote(), 'ws://localhost:5748'); + + server.once('connect', function() { + var receivedSubscribe = false; + + assert.strictEqual(server._hostid, ''); + + server.once('response_server_info', function() { + receivedSubscribe = true; + }); + + server.once('disconnect', function() { + assert(receivedSubscribe); + assert.strictEqual(server.getHostID(), 'MAC'); + done(); + }); + }); + + server.connect(); + }); });