From 3bf00d2e0853cfe791405d984abd5b69c91fad24 Mon Sep 17 00:00:00 2001 From: cryptoBeliever Date: Tue, 4 Aug 2026 13:13:48 +0200 Subject: [PATCH 1/2] [nanowallet] fix: update hardcoded testnet and mainnet node lists The bundled testnet list pointed at hugetestalice/medalice hosts and the mainnet list fell through to the nem-sdk default, both of which contain nodes that are no longer reachable. Replace the testnet list and add an explicit mainnet override in the Nodes service constructor. Also derive the testnet fallback endpoint in setDefault() from nem.model.nodes.testnet[0] instead of repeating a hostname literal, so the default node always follows the configured list. --- nanowallet/src/app/services/nodes.service.js | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/nanowallet/src/app/services/nodes.service.js b/nanowallet/src/app/services/nodes.service.js index 615f139e..cd36a73e 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' }]; @@ -194,7 +212,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")(nem.model.nodes.testnet[0].uri, nem.model.nodes.defaultPort); this._Wallet.node = endpoint; } this._Wallet.nodes = nem.model.nodes.testnet; From 93c0c4ac9e279ca28f6bda23bc7a1455195066ff Mon Sep 17 00:00:00 2001 From: cryptoBeliever Date: Tue, 4 Aug 2026 19:44:20 +0200 Subject: [PATCH 2/2] [nanowallet] fix: update hardcoded testnet and mainnet node lists - select node randomly The bundled testnet list pointed at hugetestalice/medalice hosts and the mainnet list fell through to the nem-sdk default, both of which contain nodes that are no longer reachable. Replace the testnet list and add an explicit mainnet override in the Nodes service constructor. Also derive the testnet fallback endpoint in setDefault() from nem.model.nodes.testnet[0] instead of repeating a hostname literal, so the default node always follows the configured list. --- nanowallet/src/app/services/nodes.service.js | 27 ++++-- .../specs/importanceTransferModule.spec.js | 2 +- nanowallet/tests/specs/nodes.service.spec.js | 92 ++++++++++++++++-- .../test/importanceTransferModuleTests.js | 95 +++---------------- 4 files changed, 122 insertions(+), 94 deletions(-) diff --git a/nanowallet/src/app/services/nodes.service.js b/nanowallet/src/app/services/nodes.service.js index cd36a73e..e6f99526 100644 --- a/nanowallet/src/app/services/nodes.service.js +++ b/nanowallet/src/app/services/nodes.service.js @@ -196,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; @@ -212,7 +227,7 @@ class Nodes { if (this._storage.selectedTestnetNode) { this._Wallet.node = this._storage.selectedTestnetNode; } else { - let endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.testnet[0].uri, 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; @@ -220,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; @@ -238,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)); }