diff --git a/nanowallet/src/app/services/nodes.service.js b/nanowallet/src/app/services/nodes.service.js index 615f139e..e6f99526 100644 --- a/nanowallet/src/app/services/nodes.service.js +++ b/nanowallet/src/app/services/nodes.service.js @@ -147,11 +147,29 @@ class Nodes { this._$timeout = $timeout; nem.model.nodes.testnet = [{ - uri: 'http://hugetestalice.nem.ninja' + uri: 'http://libertalia.nemtest.net' }, { - uri: 'http://hugetestalice2.nem.ninja' + uri: 'http://ocracoke.nemtest.net' }, { - uri: 'http://medalice2.nem.ninja' + uri: 'http://tortuga.nemtest.net' + }, { + uri: 'http://ntn1.dusanjp.com' + }, { + uri: 'http://localhost' + }]; + + nem.model.nodes.mainnet = [{ + uri: 'http://portobelo.nemmain.net' + }, { + uri: 'http://hugealice.nem.ninja' + }, { + uri: 'http://hugealice2.nem.ninja' + }, { + uri: 'http://hugealice3.nem.ninja' + }, { + uri: 'http://1n.dusanjp.com' + }, { + uri: 'http://2n.dusanjp.com' }, { uri: 'http://localhost' }]; @@ -178,15 +196,30 @@ class Nodes { return; } + /** + * Pick a random node uri in a list of nodes + * Local nodes are skipped, they must be selected explicitly by the user + * + * @param {array} nodes - An array of node objects + * + * @return {string} - A node uri + */ + getRandomNodeUri(nodes) { + let pool = nodes.filter((node) => node.uri !== 'http://localhost'); + if (!pool.length) pool = nodes; + return pool[Math.floor(Math.random() * pool.length)].uri; + } + /** * Check if nodes present in local storage or set default according to network + * If no node in local storage a random node is used, to balance the load between nodes */ setDefault() { if (this._Wallet.network == nem.model.network.data.mainnet.id) { if (this._storage.selectedMainnetNode) { this._Wallet.node = this._storage.selectedMainnetNode; } else { - let endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.mainnet[0].uri, nem.model.nodes.defaultPort); + let endpoint = nem.model.objects.create("endpoint")(this.getRandomNodeUri(nem.model.nodes.mainnet), nem.model.nodes.defaultPort); this._Wallet.node = endpoint; } this._Wallet.nodes = nem.model.nodes.mainnet; @@ -194,7 +227,7 @@ class Nodes { if (this._storage.selectedTestnetNode) { this._Wallet.node = this._storage.selectedTestnetNode; } else { - let endpoint = nem.model.objects.create("endpoint")("http://hugetestalice.nem.ninja", nem.model.nodes.defaultPort); + let endpoint = nem.model.objects.create("endpoint")(this.getRandomNodeUri(nem.model.nodes.testnet), nem.model.nodes.defaultPort); this._Wallet.node = endpoint; } this._Wallet.nodes = nem.model.nodes.testnet; @@ -202,7 +235,7 @@ class Nodes { if (this._storage.selectedMijinNode) { this._Wallet.node = this._storage.selectedMijinNode; } else { - let endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.mijin[0].uri, nem.model.nodes.mijinPort); + let endpoint = nem.model.objects.create("endpoint")(this.getRandomNodeUri(nem.model.nodes.mijin), nem.model.nodes.mijinPort); this._Wallet.node = endpoint; } this._Wallet.nodes = nem.model.nodes.mijin; @@ -220,13 +253,13 @@ class Nodes { let _endpoint; // Set node in local storage according to network if (this._Wallet.network == nem.model.network.data.mainnet.id) { - _endpoint = endpoint || nem.model.objects.create("endpoint")(nem.model.nodes.mainnet[Math.floor(Math.random()*nem.model.nodes.mainnet.length)].uri, nem.model.nodes.defaultPort); + _endpoint = endpoint || nem.model.objects.create("endpoint")(this.getRandomNodeUri(nem.model.nodes.mainnet), nem.model.nodes.defaultPort); this._storage.selectedMainnetNode = _endpoint; } else if (this._Wallet.network == nem.model.network.data.testnet.id) { - _endpoint = endpoint || nem.model.objects.create("endpoint")(nem.model.nodes.testnet[Math.floor(Math.random()*nem.model.nodes.testnet.length)].uri, nem.model.nodes.defaultPort); + _endpoint = endpoint || nem.model.objects.create("endpoint")(this.getRandomNodeUri(nem.model.nodes.testnet), nem.model.nodes.defaultPort); this._storage.selectedTestnetNode = _endpoint; } else { - _endpoint = endpoint || nem.model.objects.create("endpoint")(nem.model.nodes.mijin[Math.floor(Math.random()*nem.model.nodes.mijin.length)].uri, nem.model.nodes.mijinPort); + _endpoint = endpoint || nem.model.objects.create("endpoint")(this.getRandomNodeUri(nem.model.nodes.mijin), nem.model.nodes.mijinPort); this._storage.selectedMijinNode = _endpoint; } // Set endpoint in Wallet service diff --git a/nanowallet/tests/specs/importanceTransferModule.spec.js b/nanowallet/tests/specs/importanceTransferModule.spec.js index 1e0bfd98..712f9e01 100644 --- a/nanowallet/tests/specs/importanceTransferModule.spec.js +++ b/nanowallet/tests/specs/importanceTransferModule.spec.js @@ -49,7 +49,7 @@ describe('Importance transfer module tests', function() { expect(ctrl.customHarvestingNode).toEqual(""); expect(ctrl.harvestingNode).toEqual(Wallet.node); expect(ctrl.hasFreeSlots).toBe(false); - expect(ctrl.nodes[0]).toEqual(nem.model.objects.create("endpoint")("http://hugetestalice.nem.ninja", 7890)); + expect(ctrl.nodes[0]).toEqual(nem.model.objects.create("endpoint")(Wallet.nodes[0].uri, 7890)); expect(ctrl.showSupernodes).toBe(false); }); diff --git a/nanowallet/tests/specs/nodes.service.spec.js b/nanowallet/tests/specs/nodes.service.spec.js index df72e349..c2751c84 100644 --- a/nanowallet/tests/specs/nodes.service.spec.js +++ b/nanowallet/tests/specs/nodes.service.spec.js @@ -27,12 +27,75 @@ describe('Nodes Service', () => { it('sets a fixed testnet node list on nem.model.nodes', () => { // Assert: expect(nem.model.nodes.testnet).toEqual([ - { uri: 'http://hugetestalice.nem.ninja' }, - { uri: 'http://hugetestalice2.nem.ninja' }, - { uri: 'http://medalice2.nem.ninja' }, + { uri: 'http://libertalia.nemtest.net' }, + { uri: 'http://ocracoke.nemtest.net' }, + { uri: 'http://tortuga.nemtest.net' }, + { uri: 'http://ntn1.dusanjp.com' }, { uri: 'http://localhost' } ]); }); + + it('sets a fixed mainnet node list on nem.model.nodes', () => { + // Assert: + expect(nem.model.nodes.mainnet).toEqual([ + { uri: 'http://portobelo.nemmain.net' }, + { uri: 'http://hugealice.nem.ninja' }, + { uri: 'http://hugealice2.nem.ninja' }, + { uri: 'http://hugealice3.nem.ninja' }, + { uri: 'http://1n.dusanjp.com' }, + { uri: 'http://2n.dusanjp.com' }, + { uri: 'http://localhost' } + ]); + }); + }); + + describe('getRandomNodeUri', () => { + it('returns a node of the given list', () => { + // Arrange: + const nodes = [{ uri: 'http://node1' }, { uri: 'http://node2' }, { uri: 'http://node3' }]; + + // Act: + const result = nodesService.getRandomNodeUri(nodes); + + // Assert: + expect(nodes).toContain({ uri: result }); + }); + + it('can pick any node of the given list', () => { + // Arrange: + const nodes = [{ uri: 'http://node1' }, { uri: 'http://node2' }, { uri: 'http://node3' }]; + const randoms = [0, 0.4, 0.9]; + let index = 0; + spyOn(Math, 'random').and.callFake(() => randoms[index++]); + + // Act: + const result = randoms.map(() => nodesService.getRandomNodeUri(nodes)); + + // Assert: + expect(result).toEqual(['http://node1', 'http://node2', 'http://node3']); + }); + + it('never picks the local node when other nodes are available', () => { + // Arrange: + const nodes = [{ uri: 'http://node1' }, { uri: 'http://localhost' }]; + const randoms = [0, 0.5, 0.99]; + let index = 0; + spyOn(Math, 'random').and.callFake(() => randoms[index++]); + + // Act: + const result = randoms.map(() => nodesService.getRandomNodeUri(nodes)); + + // Assert: + expect(result).toEqual(['http://node1', 'http://node1', 'http://node1']); + }); + + it('picks the local node when it is the only one available', () => { + // Act: + const result = nodesService.getRandomNodeUri([{ uri: 'http://localhost' }]); + + // Assert: + expect(result).toEqual('http://localhost'); + }); }); describe('cleanEndpoint', () => { @@ -105,7 +168,7 @@ describe('Nodes Service', () => { expect(mockWallet.nodes).toBe(nem.model.nodes.mainnet); }); - it('falls back to the first bundled testnet node when nothing is stored', () => { + it('falls back to a random bundled testnet node when nothing is stored', () => { // Arrange: mockWallet.network = TESTNET_NETWORK; @@ -113,11 +176,25 @@ describe('Nodes Service', () => { nodesService.setDefault(); // Assert: - expect(mockWallet.node).toEqual( - nem.model.objects.create('endpoint')('http://hugetestalice.nem.ninja', nem.model.nodes.defaultPort) - ); + expect(nem.model.nodes.testnet).toContain({ uri: mockWallet.node.host }); + expect(mockWallet.node.host).not.toEqual('http://localhost'); + expect(mockWallet.node.port).toEqual(nem.model.nodes.defaultPort); expect(mockWallet.nodes).toBe(nem.model.nodes.testnet); }); + + it('falls back to a random bundled mainnet node when nothing is stored', () => { + // Arrange: + mockWallet.network = MAINNET_NETWORK; + + // Act: + nodesService.setDefault(); + + // Assert: + expect(nem.model.nodes.mainnet).toContain({ uri: mockWallet.node.host }); + expect(mockWallet.node.host).not.toEqual('http://localhost'); + expect(mockWallet.node.port).toEqual(nem.model.nodes.defaultPort); + expect(mockWallet.nodes).toBe(nem.model.nodes.mainnet); + }); }); describe('update', () => { @@ -143,6 +220,7 @@ describe('Nodes Service', () => { // Assert: expect(nem.model.nodes.mainnet).toContain({ uri: mockWallet.node.host }); + expect(mockWallet.node.host).not.toEqual('http://localhost'); expect(mockStorage.selectedMainnetNode).toBe(mockWallet.node); }); }); diff --git a/nanowallet/tests/test/importanceTransferModuleTests.js b/nanowallet/tests/test/importanceTransferModuleTests.js index dff3d399..a2f9eaa0 100644 --- a/nanowallet/tests/test/importanceTransferModuleTests.js +++ b/nanowallet/tests/test/importanceTransferModuleTests.js @@ -1,6 +1,19 @@ +import nem from 'nem-sdk'; import WalletFixture from '../data/wallet'; import AccountDataFixture from '../data/accountData'; +/** + * Map the node list of the Wallet service to the endpoint objects exposed by the controllers + * + * @param {object} Wallet - The Wallet service + * + * @return {array} - An array of endpoint objects + */ +const expectedEndpoints = (Wallet) => Wallet.nodes.map((node) => ({ + "host": node.uri, + "port": nem.model.nodes.defaultPort +})); + export const setupMainnetWallet = (Wallet, Nodes, DataBridge) => { Wallet.use(WalletFixture.mainnetWallet); Nodes.setDefault(); @@ -25,24 +38,7 @@ export const assertTestnetNodes = ($controller, $rootScope, controlName, Wallet, ctrl.setNodes(); // Assert: - expect(ctrl.nodes).toEqual([ - { - "host": "http://hugetestalice.nem.ninja", - "port": 7890 - }, - { - "host": "http://hugetestalice2.nem.ninja", - "port": 7890 - }, - { - "host": "http://medalice2.nem.ninja", - "port": 7890 - }, - { - "host": "http://localhost", - "port": 7890 - } - ]); + expect(ctrl.nodes).toEqual(expectedEndpoints(Wallet)); } export const assertSuperNodes = async ($controller, $rootScope, controlName, Wallet, Nodes, DataBridge, SuperNodeProgram, $timeout) => { @@ -83,66 +79,5 @@ export const assertMainnetNodes = async ($controller, $rootScope, controlName, W $timeout.flush(); // Assert: - expect(ctrl.nodes).toEqual([ - { - "host": "http://hugealice.nem.ninja", - "port": 7890 - }, - { - "host": "http://hugealice2.nem.ninja", - "port": 7890 - }, - { - "host": "http://hugealice3.nem.ninja", - "port": 7890 - }, - { - "host": "http://hugealice4.nem.ninja", - "port": 7890 - }, - { - "host": "http://bigalice3.nem.ninja", - "port": 7890 - }, - { - "host": "http://san.nem.ninja", - "port": 7890 - }, - { - "host": "http://go.nem.ninja", - "port": 7890 - }, - { - "host": "http://hachi.nem.ninja", - "port": 7890 - }, - { - "host": "http://jusan.nem.ninja", - "port": 7890 - }, - { - "host": "http://nijuichi.nem.ninja", - "port": 7890 - }, - { - "host": "http://alice5.nem.ninja", - "port": 7890 - }, - { - "host": "http://alice6.nem.ninja", - "port": 7890 - }, - { - "host": "http://alice7.nem.ninja", - "port": 7890 - }, - { - "host": "http://alice8.nem.ninja", - "port": 7890 - }, - { - "host": "http://localhost", - "port": 7890 - } - ]); + expect(ctrl.nodes).toEqual(expectedEndpoints(Wallet)); }