|
|
|
@ -4,6 +4,8 @@
|
|
|
|
|
const is = require('@sindresorhus/is');
|
|
|
|
|
const { lokiRpc } = require('./loki_rpc');
|
|
|
|
|
|
|
|
|
|
const RANDOM_SNODES_TO_USE = 3;
|
|
|
|
|
|
|
|
|
|
class LokiSnodeAPI {
|
|
|
|
|
constructor({ serverUrl, localUrl }) {
|
|
|
|
|
if (!is.string(serverUrl)) {
|
|
|
|
@ -18,6 +20,7 @@ class LokiSnodeAPI {
|
|
|
|
|
async getRandomSnodeAddress() {
|
|
|
|
|
/* resolve random snode */
|
|
|
|
|
if (this.randomSnodePool.length === 0) {
|
|
|
|
|
// allow exceptions to pass through upwards
|
|
|
|
|
await this.initialiseRandomPool();
|
|
|
|
|
}
|
|
|
|
|
if (this.randomSnodePool.length === 0) {
|
|
|
|
@ -28,7 +31,7 @@ class LokiSnodeAPI {
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async initialiseRandomPool(seedNodes = [...window.seedNodeList]) {
|
|
|
|
|
async initialiseRandomPool(seedNodes = [...window.seedNodeList], consecutiveErrors = 0) {
|
|
|
|
|
const params = {
|
|
|
|
|
limit: 20,
|
|
|
|
|
active_only: true,
|
|
|
|
@ -43,8 +46,9 @@ class LokiSnodeAPI {
|
|
|
|
|
Math.floor(Math.random() * seedNodes.length),
|
|
|
|
|
1
|
|
|
|
|
)[0];
|
|
|
|
|
let snodes = [];
|
|
|
|
|
try {
|
|
|
|
|
const result = await lokiRpc(
|
|
|
|
|
const response = await lokiRpc(
|
|
|
|
|
`http://${seedNode.ip}`,
|
|
|
|
|
seedNode.port,
|
|
|
|
|
'get_n_service_nodes',
|
|
|
|
@ -53,7 +57,7 @@ class LokiSnodeAPI {
|
|
|
|
|
'/json_rpc' // Seed request endpoint
|
|
|
|
|
);
|
|
|
|
|
// Filter 0.0.0.0 nodes which haven't submitted uptime proofs
|
|
|
|
|
const snodes = result.result.service_node_states.filter(
|
|
|
|
|
snodes = response.result.service_node_states.filter(
|
|
|
|
|
snode => snode.public_ip !== '0.0.0.0'
|
|
|
|
|
);
|
|
|
|
|
this.randomSnodePool = snodes.map(snode => ({
|
|
|
|
@ -64,12 +68,20 @@ class LokiSnodeAPI {
|
|
|
|
|
}));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log.warn('initialiseRandomPool error', e.code, e.message);
|
|
|
|
|
if (seedNodes.length === 0) {
|
|
|
|
|
throw new window.textsecure.SeedNodeError(
|
|
|
|
|
'Failed to contact seed node'
|
|
|
|
|
);
|
|
|
|
|
if (consecutiveErrors < 3) {
|
|
|
|
|
// retry after a possible delay
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
log.info('Retrying initialising random snode pool, try #', consecutiveErrors);
|
|
|
|
|
this.initialiseRandomPool(seedNodes, consecutiveErrors + 1);
|
|
|
|
|
}, consecutiveErrors * consecutiveErrors * 5000);
|
|
|
|
|
} else {
|
|
|
|
|
log.error('Giving up trying to contact seed node');
|
|
|
|
|
if (snodes.length === 0) {
|
|
|
|
|
throw new window.textsecure.SeedNodeError(
|
|
|
|
|
'Failed to contact seed node'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.initialiseRandomPool(seedNodes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -111,6 +123,7 @@ class LokiSnodeAPI {
|
|
|
|
|
const filteredNodes = newNodes.filter(snode => snode.ip !== '0.0.0.0');
|
|
|
|
|
const conversation = ConversationController.get(pubKey);
|
|
|
|
|
await conversation.updateSwarmNodes(filteredNodes);
|
|
|
|
|
return filteredNodes;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new window.textsecure.ReplayableError({
|
|
|
|
|
message: 'Could not get conversation',
|
|
|
|
@ -120,7 +133,8 @@ class LokiSnodeAPI {
|
|
|
|
|
|
|
|
|
|
async refreshSwarmNodesForPubKey(pubKey) {
|
|
|
|
|
const newNodes = await this.getFreshSwarmNodes(pubKey);
|
|
|
|
|
this.updateSwarmNodes(pubKey, newNodes);
|
|
|
|
|
const filteredNodes = this.updateSwarmNodes(pubKey, newNodes);
|
|
|
|
|
return filteredNodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getFreshSwarmNodes(pubKey) {
|
|
|
|
@ -130,6 +144,7 @@ class LokiSnodeAPI {
|
|
|
|
|
try {
|
|
|
|
|
newSwarmNodes = await this.getSwarmNodes(pubKey);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log.error('getFreshSwarmNodes error', e.code, e.message);
|
|
|
|
|
// TODO: Handle these errors sensibly
|
|
|
|
|
newSwarmNodes = [];
|
|
|
|
|
}
|
|
|
|
@ -141,9 +156,7 @@ class LokiSnodeAPI {
|
|
|
|
|
return newSwarmNodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getSwarmNodes(pubKey) {
|
|
|
|
|
// TODO: Hit multiple random nodes and merge lists?
|
|
|
|
|
const snode = await this.getRandomSnodeAddress();
|
|
|
|
|
async getSnodesForPubkey(snode, pubKey) {
|
|
|
|
|
try {
|
|
|
|
|
const result = await lokiRpc(
|
|
|
|
|
`https://${snode.ip}`,
|
|
|
|
@ -158,7 +171,7 @@ class LokiSnodeAPI {
|
|
|
|
|
);
|
|
|
|
|
if (!result) {
|
|
|
|
|
log.warn(
|
|
|
|
|
`getSwarmNodes lokiRpc on ${snode.ip}:${
|
|
|
|
|
`getSnodesForPubkey lokiRpc on ${snode.ip}:${
|
|
|
|
|
snode.port
|
|
|
|
|
} returned falsish value`,
|
|
|
|
|
result
|
|
|
|
@ -168,11 +181,32 @@ class LokiSnodeAPI {
|
|
|
|
|
const snodes = result.snodes.filter(tSnode => tSnode.ip !== '0.0.0.0');
|
|
|
|
|
return snodes;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
log.error('getSwarmNodes error', e.code, e.message);
|
|
|
|
|
log.error('getSnodesForPubkey error', e.code, e.message, `for ${snode.ip}:${snode.port}`);
|
|
|
|
|
this.markRandomNodeUnreachable(snode);
|
|
|
|
|
return this.getSwarmNodes(pubKey);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getSwarmNodes(pubKey) {
|
|
|
|
|
const snodes = [];
|
|
|
|
|
const questions = [...Array(RANDOM_SNODES_TO_USE).keys()];
|
|
|
|
|
await Promise.all(
|
|
|
|
|
questions.map(async () => {
|
|
|
|
|
// allow exceptions to pass through upwards
|
|
|
|
|
const rSnode = await this.getRandomSnodeAddress();
|
|
|
|
|
const resList = await this.getSnodesForPubkey(rSnode, pubKey);
|
|
|
|
|
// should we only activate entries that are in all results?
|
|
|
|
|
resList.map(item => {
|
|
|
|
|
const hasItem = snodes.some(hItem => item.ip === hItem.ip && item.port === hItem.port);
|
|
|
|
|
if (!hasItem) {
|
|
|
|
|
snodes.push(item);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
return snodes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = LokiSnodeAPI;
|
|
|
|
|