@ -15,6 +15,7 @@ class LokiSnodeAPI {
this . localUrl = localUrl ; // localhost.loki
this . localUrl = localUrl ; // localhost.loki
this . randomSnodePool = [ ] ;
this . randomSnodePool = [ ] ;
this . swarmsPendingReplenish = { } ;
this . swarmsPendingReplenish = { } ;
this . initialiseRandomPoolPromise = false ;
}
}
async getRandomSnodeAddress ( ) {
async getRandomSnodeAddress ( ) {
@ -35,70 +36,99 @@ class LokiSnodeAPI {
seedNodes = [ ... window . seedNodeList ] ,
seedNodes = [ ... window . seedNodeList ] ,
consecutiveErrors = 0
consecutiveErrors = 0
) {
) {
const params = {
// if currently not in progress
limit : 20 ,
if ( this . initialiseRandomPoolPromise === false ) {
active _only : true ,
// FIXME: add timeout
fields : {
// set lock
public _ip : true ,
this . initialiseRandomPoolPromise = new Promise ( async resolve => {
storage _port : true ,
const params = {
pubkey _x25519 : true ,
limit : 1024 ,
pubkey _ed25519 : true ,
active _only : true ,
} ,
fields : {
} ;
public _ip : true ,
const seedNode = seedNodes . splice (
storage _port : true ,
Math . floor ( Math . random ( ) * seedNodes . length ) ,
pubkey _x25519 : true ,
1
pubkey _ed25519 : true ,
) [ 0 ] ;
} ,
let snodes = [ ] ;
} ;
try {
const seedNode = seedNodes . splice (
const response = await lokiRpc (
Math . floor ( Math . random ( ) * seedNodes . length ) ,
` http:// ${ seedNode . ip } ` ,
1
seedNode . port ,
) [ 0 ] ;
'get_n_service_nodes' ,
let snodes = [ ] ;
params ,
try {
{ } , // Options
log . info ( 'loki_snodes: Refreshing random snode pool' ) ;
'/json_rpc' // Seed request endpoint
const response = await lokiRpc (
) ;
` http:// ${ seedNode . ip } ` ,
// Filter 0.0.0.0 nodes which haven't submitted uptime proofs
seedNode . port ,
snodes = response . result . service _node _states . filter (
'get_n_service_nodes' ,
snode => snode . public _ip !== '0.0.0.0'
params ,
) ;
{ } , // Options
this . randomSnodePool = snodes . map ( snode => ( {
'/json_rpc' // Seed request endpoint
ip : snode . public _ip ,
port : snode . storage _port ,
pubkey _x25519 : snode . pubkey _x25519 ,
pubkey _ed25519 : snode . pubkey _ed25519 ,
} ) ) ;
} catch ( e ) {
log . warn ( 'initialiseRandomPool error' , e . code , e . message ) ;
if ( consecutiveErrors < 3 ) {
// retry after a possible delay
setTimeout ( ( ) => {
log . info (
'Retrying initialising random snode pool, try #' ,
consecutiveErrors
) ;
) ;
this . initialiseRandomPool ( seedNodes , consecutiveErrors + 1 ) ;
// Filter 0.0.0.0 nodes which haven't submitted uptime proofs
} , consecutiveErrors * consecutiveErrors * 5000 ) ;
snodes = response . result . service _node _states . filter (
} else {
snode => snode . public _ip !== '0.0.0.0'
log . error ( 'Giving up trying to contact seed node' ) ;
if ( snodes . length === 0 ) {
throw new window . textsecure . SeedNodeError (
'Failed to contact seed node'
) ;
) ;
this . randomSnodePool = snodes . map ( snode => ( {
ip : snode . public _ip ,
port : snode . storage _port ,
pubkey _x25519 : snode . pubkey _x25519 ,
pubkey _ed25519 : snode . pubkey _ed25519 ,
} ) ) ;
log . info ( 'loki_snodes: Refreshed random snode pool with' , this . randomSnodePool . length , 'snodes' ) ;
} catch ( e ) {
log . warn ( 'loki_snodes: initialiseRandomPool error' , e . code , e . message ) ;
if ( consecutiveErrors < 3 ) {
// retry after a possible delay
setTimeout ( ( ) => {
log . info (
'loki_snodes: Retrying initialising random snode pool, try #' ,
consecutiveErrors
) ;
this . initialiseRandomPool ( seedNodes , consecutiveErrors + 1 ) ;
} , consecutiveErrors * consecutiveErrors * 5000 ) ;
} else {
log . error ( 'loki_snodes: Giving up trying to contact seed node' ) ;
if ( snodes . length === 0 ) {
throw new window . textsecure . SeedNodeError (
'Failed to contact seed node'
) ;
}
}
}
}
}
// clear lock
this . initialiseRandomPoolPromise = null ;
resolve ( ) ;
} )
}
}
await this . initialiseRandomPoolPromise ;
}
}
// nodeUrl is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode
// unreachableNode.u rl is like 9hrje1bymy7hu6nmtjme9idyu3rm8gr3mkstakjyuw1997t7w4ny.snode
async unreachableNode ( pubKey , nodeUrl ) {
async unreachableNode ( pubKey , u nreachableN ode) {
const conversation = ConversationController . get ( pubKey ) ;
const conversation = ConversationController . get ( pubKey ) ;
const swarmNodes = [ ... conversation . get ( 'swarmNodes' ) ] ;
const swarmNodes = [ ... conversation . get ( 'swarmNodes' ) ] ;
if ( typeof ( unreachableNode ) === 'string' ) {
log . warn ( 'loki_snodes::unreachableNode: String passed as unreachableNode to unreachableNode' ) ;
return swarmNodes ;
}
let found = false
const filteredNodes = swarmNodes . filter (
const filteredNodes = swarmNodes . filter (
node => node . address !== nodeUrl && node . ip !== nodeUrl
node => {
// keep all but thisNode
const thisNode = ( node . address === unreachableNode . address && node . ip === unreachableNode . ip && node . port === unreachableNode . port )
if ( thisNode ) {
found = true
}
return ! thisNode
}
) ;
) ;
if ( ! found ) {
log . warn ( ` loki_snodes::unreachableNode snode ${ unreachableNode . ip } : ${ unreachableNode . port } has already been marked as bad ` ) ;
}
await conversation . updateSwarmNodes ( filteredNodes ) ;
await conversation . updateSwarmNodes ( filteredNodes ) ;
return filteredNodes ;
}
}
markRandomNodeUnreachable ( snode ) {
markRandomNodeUnreachable ( snode ) {
@ -106,6 +136,7 @@ class LokiSnodeAPI {
this . randomSnodePool ,
this . randomSnodePool ,
_ . find ( this . randomSnodePool , { ip : snode . ip , port : snode . port } )
_ . find ( this . randomSnodePool , { ip : snode . ip , port : snode . port } )
) ;
) ;
return this . randomSnodePool . length ;
}
}
async updateLastHash ( snode , hash , expiresAt ) {
async updateLastHash ( snode , hash , expiresAt ) {
@ -150,7 +181,7 @@ class LokiSnodeAPI {
try {
try {
newSwarmNodes = await this . getSwarmNodes ( pubKey ) ;
newSwarmNodes = await this . getSwarmNodes ( pubKey ) ;
} catch ( e ) {
} catch ( e ) {
log . error ( ' getFreshSwarmNodes error', e . code , e . message ) ;
log . error ( ' loki_snodes: getFreshSwarmNodes error', e . code , e . message ) ;
// TODO: Handle these errors sensibly
// TODO: Handle these errors sensibly
newSwarmNodes = [ ] ;
newSwarmNodes = [ ] ;
}
}
@ -184,16 +215,25 @@ class LokiSnodeAPI {
) ;
) ;
return [ ] ;
return [ ] ;
}
}
if ( ! result . snodes ) {
log . warn (
` getSnodesForPubkey lokiRpc on ${ snode . ip } : ${
snode . port
} returned falsish value for snodes ` ,
result
) ;
return [ ] ;
}
const snodes = result . snodes . filter ( tSnode => tSnode . ip !== '0.0.0.0' ) ;
const snodes = result . snodes . filter ( tSnode => tSnode . ip !== '0.0.0.0' ) ;
return snodes ;
return snodes ;
} catch ( e ) {
} catch ( e ) {
const randomPoolRemainingCount = this . markRandomNodeUnreachable ( snode ) ;
log . error (
log . error (
'getSnodesForPubkey error' ,
' loki_snodes: getSnodesForPubkey error',
e . code ,
e . code ,
e . message ,
e . message ,
` for ${ snode . ip } : ${ snode . port } `
` for ${ snode . ip } : ${ snode . port } . ${ randomPoolRemainingCount } snodes remaining in randomPool `
) ;
) ;
this . markRandomNodeUnreachable ( snode ) ;
return [ ] ;
return [ ] ;
}
}
}
}