From 86c03ee0f0c7b709de3d70613ecade2d28e39de0 Mon Sep 17 00:00:00 2001 From: pasta Date: Mon, 5 Oct 2026 14:00:14 -0400 Subject: [PATCH] fix(sdk): honor explicit regtest addresses in the JS DAPI client On regtest, ListDAPIAddressProvider rewrote every address to https://127.0.0.1:2443+i*100, including explicitly configured `dapiAddresses` and `seeds`, silently redirecting clients of local networks that use other ports. The rewrite exists because local masternodes register a docker-internal IP that the host cannot reach. Apply it only to addresses discovered from the masternode list, and keep their registered port, which is the gateway port dashmate publishes on the host. The `local` network preset and the bench-suite seed parser relied on the old rewrite and now name the self-signed gateway explicitly. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../client/createClientWithFundedWallet.js | 2 +- .../lib/client/parseDAPISeedsString.js | 13 +++---- .../ListDAPIAddressProvider.js | 24 +------------ ...lifiedMasternodeListDAPIAddressProvider.js | 18 +++++++++- .../createDAPIAddressProviderFromOptions.js | 1 + packages/js-dapi-client/lib/networkConfigs.js | 2 +- .../ListDAPIAddressProvider.spec.js | 14 +++----- ...dMasternodeListDAPIAddressProvider.spec.js | 35 +++++++++++++++++++ ...eateDAPIAddressProviderFromOptions.spec.js | 8 +++++ 9 files changed, 72 insertions(+), 45 deletions(-) diff --git a/packages/bench-suite/lib/client/createClientWithFundedWallet.js b/packages/bench-suite/lib/client/createClientWithFundedWallet.js index 8070aae90fe..33fb6833afb 100644 --- a/packages/bench-suite/lib/client/createClientWithFundedWallet.js +++ b/packages/bench-suite/lib/client/createClientWithFundedWallet.js @@ -11,7 +11,7 @@ const createPlatformProofVerifier = require('./createPlatformProofVerifier'); * * @param {number} amount * @param {Object} config - * @param {{host: string, port: string}[]} config.seeds + * @param {RawDAPIAddress[]} config.seeds * @param {string} config.network * @param {string} config.faucetPrivateKey * @param {number} [config.skipSyncBeforeHeight] diff --git a/packages/bench-suite/lib/client/parseDAPISeedsString.js b/packages/bench-suite/lib/client/parseDAPISeedsString.js index c570feda361..924a92ff1cb 100644 --- a/packages/bench-suite/lib/client/parseDAPISeedsString.js +++ b/packages/bench-suite/lib/client/parseDAPISeedsString.js @@ -1,18 +1,13 @@ +const DAPIAddress = require('@dashevo/dapi-client/lib/dapiAddressProvider/DAPIAddress'); + /** * @param {string} seedsString - * @returns {{host: string, port: string}[]} + * @returns {RawDAPIAddress[]} */ function parseDAPISeedsString(seedsString) { return seedsString .split(',') - .map((seed) => { - const [host, port] = seed.split(':'); - - return { - host, - port, - }; - }); + .map((seed) => new DAPIAddress(seed).toJSON()); } module.exports = parseDAPISeedsString; diff --git a/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js b/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js index c13217e98d4..74fe7b2f181 100644 --- a/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js +++ b/packages/js-dapi-client/lib/dapiAddressProvider/ListDAPIAddressProvider.js @@ -1,5 +1,4 @@ const sample = (arr) => arr[Math.floor(Math.random() * arr.length)]; -const networks = require('@dashevo/dashcore-lib/lib/networks'); class ListDAPIAddressProvider { /** @@ -20,28 +19,7 @@ class ListDAPIAddressProvider { * @returns {Promise} */ async getLiveAddress() { - const liveAddresses = this.getLiveAddresses(); - - const liveAddress = sample(liveAddresses); - - if (liveAddress === undefined) { - return undefined; - } - - // This is a temporary fix for a localhost masternode. - // On macOS, internal docker IP is used to register masternode, and it's - // not really possible to bind to that address, so that workaround is introduced. - const network = networks.get(this.options.network); - if (network && network.regtestEnabled) { - const randomNodeIndex = Math.floor(Math.random() * liveAddresses.length); - - liveAddress.protocol = 'https'; - liveAddress.host = '127.0.0.1'; - liveAddress.allowSelfSignedCertificate = true; - liveAddress.port = 2443 + randomNodeIndex * 100; - } - - return liveAddress; + return sample(this.getLiveAddresses()); } /** diff --git a/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js b/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js index c6607e843eb..d6951b4581e 100644 --- a/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js +++ b/packages/js-dapi-client/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js @@ -1,3 +1,5 @@ +const networks = require('@dashevo/dashcore-lib/lib/networks'); + const DAPIAddress = require('./DAPIAddress'); class SimplifiedMasternodeListDAPIAddressProvider { @@ -5,9 +7,11 @@ class SimplifiedMasternodeListDAPIAddressProvider { * @param {SimplifiedMasternodeListProvider} smlProvider * @param {ListDAPIAddressProvider} listDAPIAddressProvider * @param {DAPIAddress[]} addressWhiteList + * @param {DAPIClientOptions} [options] */ - constructor(smlProvider, listDAPIAddressProvider, addressWhiteList) { + constructor(smlProvider, listDAPIAddressProvider, addressWhiteList, options = {}) { this.smlProvider = smlProvider; + this.options = options; this.listDAPIAddressProvider = listDAPIAddressProvider; this.addressWhiteStrings = addressWhiteList.map((dapiAddress) => dapiAddress.toString()); } @@ -58,6 +62,18 @@ class SimplifiedMasternodeListDAPIAddressProvider { )); } + // Local (regtest) masternodes register a docker-internal IP that is not + // reachable from the host, while their gateways are published on the host's + // loopback at the registered port with a self-signed certificate. + const network = networks.get(this.options.network); + if (network && network.regtestEnabled) { + filteredAddresses.forEach((address) => { + address.setHost('127.0.0.1'); + // eslint-disable-next-line no-param-reassign + address.allowSelfSignedCertificate = true; + }); + } + this.listDAPIAddressProvider.setAddresses(filteredAddresses); return this.listDAPIAddressProvider.getLiveAddress(); diff --git a/packages/js-dapi-client/lib/dapiAddressProvider/createDAPIAddressProviderFromOptions.js b/packages/js-dapi-client/lib/dapiAddressProvider/createDAPIAddressProviderFromOptions.js index 5a02a00b58d..8e767f4ef27 100644 --- a/packages/js-dapi-client/lib/dapiAddressProvider/createDAPIAddressProviderFromOptions.js +++ b/packages/js-dapi-client/lib/dapiAddressProvider/createDAPIAddressProviderFromOptions.js @@ -94,6 +94,7 @@ function createDAPIAddressProviderFromOptions(options) { smlProvider, listDAPIAddressProvider, dapiAddressesWhiteList.map((rawAddress) => new DAPIAddress(rawAddress)), + options, ); } diff --git a/packages/js-dapi-client/lib/networkConfigs.js b/packages/js-dapi-client/lib/networkConfigs.js index e45953ac3a1..24fe3d6b0e4 100644 --- a/packages/js-dapi-client/lib/networkConfigs.js +++ b/packages/js-dapi-client/lib/networkConfigs.js @@ -40,7 +40,7 @@ module.exports = { ], }, local: { - dapiAddresses: ['127.0.0.1'], + dapiAddresses: ['127.0.0.1:2443:self-signed'], network: 'regtest', }, mainnet: { diff --git a/packages/js-dapi-client/test/unit/dapiAddressProvider/ListDAPIAddressProvider.spec.js b/packages/js-dapi-client/test/unit/dapiAddressProvider/ListDAPIAddressProvider.spec.js index 8b887a4ad78..619e4bf34c0 100644 --- a/packages/js-dapi-client/test/unit/dapiAddressProvider/ListDAPIAddressProvider.spec.js +++ b/packages/js-dapi-client/test/unit/dapiAddressProvider/ListDAPIAddressProvider.spec.js @@ -100,21 +100,15 @@ describe('ListDAPIAddressProvider', () => { expect(address).to.be.undefined(); }); - it('should return modified address for localhost node', async () => { - options = { - network: 'local', - }; - + it('should not modify addresses on regtest', async () => { listDAPIAddressProvider = new ListDAPIAddressProvider( - addresses, - options, + [new DAPIAddress('127.0.0.1:45003:no-ssl')], + { network: 'local' }, ); const liveAddress = await listDAPIAddressProvider.getLiveAddress(); - expect(liveAddress.host).to.equal('127.0.0.1'); - expect(liveAddress.protocol).to.equal('https'); - expect(liveAddress.allowSelfSignedCertificate).to.be.true(); + expect(liveAddress.toString()).to.equal('http://127.0.0.1:45003'); }); }); diff --git a/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js b/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js index f130795e44e..5192f449820 100644 --- a/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js +++ b/packages/js-dapi-client/test/unit/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.spec.js @@ -3,6 +3,7 @@ const SimplifiedMNListEntry = require('@dashevo/dashcore-lib/lib/deterministicmn const DAPIAddress = require('../../../lib/dapiAddressProvider/DAPIAddress'); const SimplifiedMasternodeListDAPIAddressProvider = require('../../../lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider'); +const ListDAPIAddressProvider = require('../../../lib/dapiAddressProvider/ListDAPIAddressProvider'); describe('SimplifiedMasternodeListDAPIAddressProvider', () => { let smlDAPIAddressProvider; @@ -196,6 +197,40 @@ describe('SimplifiedMasternodeListDAPIAddressProvider', () => { }); }); + describe('#getLiveAddress on regtest', () => { + it('should point discovered addresses to the local gateways', async () => { + const options = { network: 'local' }; + const listDAPIAddressProvider = new ListDAPIAddressProvider([], options); + + smlDAPIAddressProvider = new SimplifiedMasternodeListDAPIAddressProvider( + smlProviderMock, + listDAPIAddressProvider, + [], + options, + ); + + const expectedAddresses = validMasternodeList.map((smlEntry) => ({ + host: '127.0.0.1', + port: smlEntry.platformHTTPPort, + protocol: 'https', + allowSelfSignedCertificate: true, + proRegTxHash: smlEntry.proRegTxHash, + })); + + await smlDAPIAddressProvider.getLiveAddress(); + + const addressPool = listDAPIAddressProvider.getAllAddresses(); + expect(addressPool.map((address) => address.toJSON())).to.deep.equal(expectedAddresses); + + // A refresh reuses the same address objects, so their ban state is kept + await smlDAPIAddressProvider.getLiveAddress(); + + const refreshedPool = listDAPIAddressProvider.getAllAddresses(); + expect(refreshedPool.map((address) => address.toJSON())).to.deep.equal(expectedAddresses); + refreshedPool.forEach((address, index) => expect(address).to.equal(addressPool[index])); + }); + }); + describe('#hasLiveAddresses', () => { it('should return ListAddressProvider#hasLiveAddresses result', async () => { const result = await smlDAPIAddressProvider.hasLiveAddresses(); diff --git a/packages/js-dapi-client/test/unit/dapiAddressProvider/createDAPIAddressProviderFromOptions.spec.js b/packages/js-dapi-client/test/unit/dapiAddressProvider/createDAPIAddressProviderFromOptions.spec.js index 52710eec1bb..40e8c830cb1 100644 --- a/packages/js-dapi-client/test/unit/dapiAddressProvider/createDAPIAddressProviderFromOptions.spec.js +++ b/packages/js-dapi-client/test/unit/dapiAddressProvider/createDAPIAddressProviderFromOptions.spec.js @@ -144,6 +144,14 @@ describe('createDAPIAddressProviderFromOptions', () => { expect(result).to.be.an.instanceOf(SimplifiedMasternodeListDAPIAddressProvider); }); + it('should connect `local` network to the local gateway', async () => { + const liveAddress = await createDAPIAddressProviderFromOptions({ network: 'local' }) + .getLiveAddress(); + + expect(liveAddress.toString()).to.equal('https://127.0.0.1:2443'); + expect(liveAddress.isSelfSignedCertificateAllowed()).to.be.true(); + }); + it('should throw DAPIClientError if there is no config for a specified network', async () => { options.network = 'unknown';