Skip to content
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const sample = (arr) => arr[Math.floor(Math.random() * arr.length)];
const networks = require('@dashevo/dashcore-lib/lib/networks');

class ListDAPIAddressProvider {
/**
Expand All @@ -20,28 +19,11 @@ class ListDAPIAddressProvider {
* @returns {Promise<DAPIAddress|undefined>}
*/
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;
// Addresses are returned as configured. The regtest compatibility rewrite
// for masternode-list addresses lives in
// SimplifiedMasternodeListDAPIAddressProvider, so an explicit list is
// never rewritten.
return sample(this.getLiveAddresses());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
const networks = require('@dashevo/dashcore-lib/lib/networks');

const DAPIAddress = require('./DAPIAddress');

class SimplifiedMasternodeListDAPIAddressProvider {
/**
* @param {SimplifiedMasternodeListProvider} smlProvider
* @param {ListDAPIAddressProvider} listDAPIAddressProvider
* @param {DAPIAddress[]} addressWhiteList
* @param {DAPIClientOptions} [options] - defaults to the list provider's options
*/
constructor(smlProvider, listDAPIAddressProvider, addressWhiteList) {
constructor(smlProvider, listDAPIAddressProvider, addressWhiteList, options) {
this.smlProvider = smlProvider;
// A caller of the original three-argument constructor still gets the
// regtest rewrite: the list provider holds the same client options.
this.options = options ?? listDAPIAddressProvider.options ?? {};
this.listDAPIAddressProvider = listDAPIAddressProvider;
this.addressWhiteStrings = addressWhiteList.map((dapiAddress) => dapiAddress.toString());
}
Expand All @@ -34,7 +40,28 @@ class SimplifiedMasternodeListDAPIAddressProvider {
addressesByRegProTxHashes[address.getProRegTxHash()] = address;
});

const updatedAddresses = validMasternodeList.map((smlEntry) => {
// 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.
//
// It applies here, and only here, because only addresses discovered from
// the masternode list can hold such an unreachable docker-internal host. An
// address list the caller supplies (`dapiAddresses`, `seeds`) already names
// the exact gateway to talk to (dashmate e2e suites move the stock ports on
// purpose), and clobbering it with the stock local ports silently redirects
// every request to whichever network squats those ports on the machine.
//
// The discovered pool is rewritten before it is published, not the address
// this method returns: the masternode list stream selects from the same
// list provider directly on every (re)connect.
const network = networks.get(this.options.network);
const isRegtest = Boolean(network && network.regtestEnabled);

// Each masternode's gateway port follows its position in the full list, so
// white-listing some masternodes does not move the others to other ports.
const gatewayPorts = new Map();

const updatedAddresses = validMasternodeList.map((smlEntry, index) => {
let address = addressesByRegProTxHashes[smlEntry.proRegTxHash];

if (!address) {
Expand All @@ -46,8 +73,15 @@ class SimplifiedMasternodeListDAPIAddressProvider {
});
} else {
address.setHost(smlEntry.getIp());

if (isRegtest) {
// Undo the last rewrite, so the white list sees the registered endpoint
address.setPort(smlEntry.platformHTTPPort);
}
}

gatewayPorts.set(address, 2443 + index * 100);

return address;
});

Expand All @@ -58,6 +92,17 @@ class SimplifiedMasternodeListDAPIAddressProvider {
));
}

if (isRegtest) {
filteredAddresses.forEach((address) => {
/* eslint-disable no-param-reassign */
address.protocol = 'https';
address.host = '127.0.0.1';
address.allowSelfSignedCertificate = true;
address.port = gatewayPorts.get(address);
/* eslint-enable no-param-reassign */
});
}

Comment thread
PastaPastaPasta marked this conversation as resolved.
this.listDAPIAddressProvider.setAddresses(filteredAddresses);

return this.listDAPIAddressProvider.getLiveAddress();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ function createDAPIAddressProviderFromOptions(options) {
smlProvider,
listDAPIAddressProvider,
dapiAddressesWhiteList.map((rawAddress) => new DAPIAddress(rawAddress)),
options,
);
}

Expand Down
4 changes: 3 additions & 1 deletion packages/js-dapi-client/lib/networkConfigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ module.exports = {
],
},
local: {
dapiAddresses: ['127.0.0.1'],
// The local dashmate gateway: stock port, self-signed TLS
// (see scripts/configure_test_suite.sh).
dapiAddresses: ['127.0.0.1:2443:self-signed'],
network: 'regtest',
},
mainnet: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,72 @@ describe('ListDAPIAddressProvider', () => {
expect(address).to.be.undefined();
});

it('should return modified address for localhost node', async () => {
it('should not modify an explicit address that carries a proRegTxHash', async () => {
options = {
network: 'local',
};

// The regtest rewrite belongs to the masternode-list provider. A list
// the caller supplies is used as given, even when an entry names the
// masternode it belongs to.
const explicitAddress = new DAPIAddress({
host: '127.0.0.2',
port: 45003,
proRegTxHash: 'a'.repeat(64),
});

listDAPIAddressProvider = new ListDAPIAddressProvider(
addresses,
[explicitAddress],
options,
);

const liveAddress = await listDAPIAddressProvider.getLiveAddress();

expect(liveAddress.host).to.equal('127.0.0.2');
expect(liveAddress.port).to.equal(45003);
expect(liveAddress.allowSelfSignedCertificate).to.be.false();
});

it('should not modify a caller-supplied non-loopback address', async () => {
options = {
network: 'local',
};

// A caller-supplied address names the exact gateway to talk to, even
// when the host is a secondary loopback, LAN IP, or container hostname.
const explicitAddress = new DAPIAddress('127.0.0.2:45003:self-signed');

listDAPIAddressProvider = new ListDAPIAddressProvider(
[explicitAddress],
options,
);

const liveAddress = await listDAPIAddressProvider.getLiveAddress();

expect(liveAddress.host).to.equal('127.0.0.2');
expect(liveAddress.port).to.equal(45003);
expect(liveAddress.allowSelfSignedCertificate).to.be.true();
});

it('should not modify an explicitly configured loopback address', async () => {
options = {
network: 'local',
};

// A local network that moved its ports off the stock 2443 range
// (dashmate e2e suites do) is addressed explicitly; rewriting the port
// would redirect every request to whatever squats the stock ports.
const loopbackAddress = new DAPIAddress('127.0.0.1:45003:self-signed');

listDAPIAddressProvider = new ListDAPIAddressProvider(
[loopbackAddress],
options,
);

const liveAddress = await listDAPIAddressProvider.getLiveAddress();

expect(liveAddress.host).to.equal('127.0.0.1');
expect(liveAddress.protocol).to.equal('https');
expect(liveAddress.port).to.equal(45003);
expect(liveAddress.allowSelfSignedCertificate).to.be.true();
});
});
Expand Down
Loading
Loading