ipfs-coord
Version:
A JS library for helping IPFS peers coordinate, find a common interest, and stay connected around that interest.
358 lines (295 loc) • 11.7 kB
JavaScript
/*
This Controller library is concerned with timer-based functions that are
kicked off periodicially. These functions maintain connections and state
of the IPFS node.
*/
const DEFAULT_COORDINATION_ROOM = 'psf-ipfs-coordination-002'
let _this
class TimerControllers {
constructor (localConfig = {}) {
// Dependency Injection
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters required when instantiating Timer Controllers'
)
}
this.statusLog = localConfig.statusLog
if (!this.statusLog) {
throw new Error(
'Handler for status logs required when instantiating Timer Controllers'
)
}
this.debugLevel = localConfig.debugLevel
this.config = localConfig
_this = this
}
startTimers (thisNode, useCases) {
// Periodically maintain the connection to Circuit Relays.
this.circuitRelayTimerHandle = setInterval(async function () {
await _this.manageCircuitRelays(thisNode, useCases)
}, 60000) // One Minute
// Periodically announce this nodes existance to the network.
this.announceTimerHandle = setInterval(async function () {
await _this.manageAnnouncement(thisNode, useCases)
}, 31000)
// Periodically maintain the connection to other coordination peers.
this.peerTimerHandle = setInterval(async function () {
await _this.managePeers(thisNode, useCases)
}, 2 * 50000)
// Periodically try to connect to problematic peers that advertise as
// potential circuit relays.
this.relaySearch = setInterval(async function () {
await _this.searchForRelays(thisNode, useCases)
}, 3 * 60000)
// Periodically ensure we are disconnected from blacklisted peers.
this.checkBlacklist = setInterval(async function () {
await _this.blacklist(thisNode, useCases)
}, 30000)
this.bwTimerHandle = setInterval(async function () {
// monitorBandwidth() throws an error in browsers.
// if (_this.config.type !== 'browser') {
// await _this.monitorBandwidth(thisNode, useCases)
// }
// console.log('_this.adapters.ipfs: ', _this.adapters.ipfs)
const ipfs = _this.adapters.ipfs.ipfs
const pubsubChans = await ipfs.pubsub.ls()
// console.log(`subscribed pubsub channels: ${JSON.stringify(pubsubChans, null, 2)}`)
_this.adapters.log.statusLog(2, `subscribed pubsub channels: ${JSON.stringify(pubsubChans, null, 2)}`)
}, 32000)
// Return handles to the different timers.
return {
circuitRelayTimerHandle: this.circuitRelayTimerHandle,
announceTimerHandle: this.announceTimerHandle,
peerTimerHandle: this.peerTimerHandle,
relaySearch: this.relaySearch,
checkBlacklist: this.checkBlacklist
}
}
// Used mostly for testing. Ensures all timers are stopped.
async stopAllTimers () {
clearInterval(this.circuitRelayTimerHandle)
clearInterval(this.announceTimerHandle)
clearInterval(this.peerTimerHandle)
clearInterval(this.relaySearch)
clearInterval(this.checkBlacklist)
clearInterval(this.bwTimerHandle)
}
// Monitor the bandwidth being consumed by IPFS peers. Disconnect from peers
// that request too much bandwidth.
async monitorBandwidth (thisNode, useCases) {
try {
const ipfs = this.adapters.ipfs.ipfs
// const bw = await ipfs.stats.bw()
// console.log('bw: ', bw)
for await (const stats of ipfs.stats.bw()) {
// console.log(stats)
this.adapters.log.statusLog(2, 'Bandwidth stats: ', stats)
// this.adapters.log.statusLog(2, `${JSON.stringify(stats, null, 2)}`)
}
const bitswap = await ipfs.stats.bitswap()
this.adapters.log.statusLog(2, 'bitswap stats: ', bitswap)
// this.adapters.log.statusLog(2, `${JSON.stringify(bitswap, null, 2)}`)
// Monitor pubsub channels.
const pubsubs = await ipfs.pubsub.ls()
// this.adapters.log.statusLog(2, 'pubsubs: ', pubsubs)
this.adapters.log.statusLog(
2,
`pubsubs: ${JSON.stringify(pubsubs, null, 2)}`
)
// Shut down node if it runs-away with connections.
const MAX_PEERS = 50
if (bitswap.peers.length > MAX_PEERS) {
console.log(
`IPFS node is misbehaving, by connecting to more than ${MAX_PEERS} peers. Shutting down for 10 seconds.`
)
await ipfs.stop()
await sleep(10000)
await ipfs.start()
}
} catch (err) {
console.error('Error in timer-controller.js/monitorBandwidth(): ', err)
this.adapters.log.statusLog(
2,
'Error in timer-controller.jsmonitorBandwidth(): ',
err
)
// Note: Do not throw an error. This is a top-level function.
}
}
// This function is intended to be called periodically by setInterval().
async manageCircuitRelays (thisNode, useCases) {
try {
console.log('Entering manageCircuitRelays() Controller.')
// Disable the timer while processing is happening.
clearInterval(this.circuitRelayTimerHandle)
// Remove any duplicate entries
useCases.relays.removeDuplicates(thisNode)
// Maintain connections to Relays.
await useCases.relays.connectToCRs(thisNode)
// Update metrics on Relays.
await useCases.relays.measureRelays(thisNode)
const now = new Date()
this.adapters.log.statusLog(
1,
`Renewed connections to all circuit relays at ${now.toLocaleString()}`
)
// console.log('Exiting manageCircuitRelays() Controller.')
} catch (err) {
console.error(
'Error in timer-controller.js/manageCircuitRelays(): ',
err
)
this.adapters.log.statusLog(
2,
'Error in timer-controller.js/manageCircuitRelays(): ',
err
)
// Note: Do not throw an error. This is a top-level function.
}
// Periodically maintain the connection to Circuit Relays.
this.circuitRelayTimerHandle = setInterval(async function () {
await _this.manageCircuitRelays(thisNode, useCases)
}, 60000) // One Minute
}
// This function is intended to be called periodically by setInterval().
// Announce the existance of this node to the network.
async manageAnnouncement (thisNode, useCases) {
try {
// console.log('thisNode: ', thisNode)
// Get the information needed for the announcement.
const announceObj = {
ipfsId: thisNode.ipfsId,
ipfsMultiaddrs: thisNode.ipfsMultiaddrs,
type: thisNode.type,
// orbitdbId: thisNode.orbit.id,
// TODO: Allow node.js apps to pass a config setting to override this.
isCircuitRelay: false
}
// Generate the announcement message.
const announceMsgObj = thisNode.schema.announcement(announceObj)
// console.log(`announceMsgObj: ${JSON.stringify(announceMsgObj, null, 2)}`)
const announceMsgStr = JSON.stringify(announceMsgObj)
// Publish the announcement to the pubsub channel.
await this.adapters.pubsub.messaging.publishToPubsubChannel(
DEFAULT_COORDINATION_ROOM,
announceMsgStr
)
if (this.debugLevel) {
const now = new Date()
this.statusLog(
`status: Announced self on ${DEFAULT_COORDINATION_ROOM} pubsub channel at ${now.toLocaleString()}`
)
}
return true
} catch (err) {
console.error('Error in timer-controller.js/manageAnnouncement(): ', err)
this.adapters.log.statusLog(
2,
'Error in timer-controller.js/manageAnnouncement(): ',
err
)
// Note: Do not throw an error. This is a top-level function.
}
}
// This function is intended to be called periodically by setInterval().
// It refreshes the connection to all subnet peers thisNode is trying to track.
async managePeers (thisNode, useCases) {
let success = false
try {
// Disable the timer while processing is happening.
clearInterval(this.peerTimerHandle)
// this.statusLog('managePeers')
await useCases.thisNode.refreshPeerConnections()
// console.error('Error in timer-controller.js/manageAnnouncement(): ', err)
this.adapters.log.statusLog(
1,
'Renewed connections to all subnet peers.'
)
success = true
} catch (err) {
this.adapters.log.statusLog(
2,
'Error in timer-controller.js/managePeers(): ',
err
)
// Note: Do not throw an error. This is a top-level function.
success = false
}
// Reinstate the timer interval
this.peerTimerHandle = setInterval(async function () {
await _this.managePeers(thisNode, useCases)
}, 21000)
return success
}
// Actively disconnect from blacklisted peers.
async blacklist (thisNode, useCases) {
let success = false
try {
// Disable the timer while processing is happening.
clearInterval(this.checkBlacklist)
// this.statusLog('managePeers')
// await useCases.thisNode.enforceBlacklist()
await useCases.thisNode.enforceWhitelist()
this.adapters.log.statusLog(1, 'Finished enforcing whitelist.')
success = true
} catch (err) {
this.adapters.log.statusLog(
2,
'Error in timer-controller.js/blacklist(): ',
err
)
// Note: Do not throw an error. This is a top-level function.
success = false
}
// Reinstate the timer interval
this.checkBlacklist = setInterval(async function () {
await _this.blacklist(thisNode, useCases)
}, 30000)
return success
}
// This method looks for subnet peers that have the isCircuitRelay flag set,
// but are not in the list of known relays. These represent potential relays
// that thisNode could not connect to, but it might be able to with another
// try.
async searchForRelays (thisNode, useCases) {
try {
// console.log('Entering searchForRelays() Controller.')
// Disable the timer while processing is happening.
clearInterval(this.relaySearch)
// Get all the known relays.
const knownRelays = thisNode.relayData.map(x => x.ipfsId)
// console.log('knownRelays: ', knownRelays)
// Get all subnet peers that have their circuit relay flag set.
let relayPeers = thisNode.peerData.filter(x => x.data.isCircuitRelay)
relayPeers = relayPeers.map(x => x.from)
// console.log('relayPeers: ', relayPeers)
// Diff the two arrays to get relays peers that are not in the relay list.
const diffRelayPeers = relayPeers.filter(x => !knownRelays.includes(x))
// console.log('diffRelayPeers: ', diffRelayPeers)
// Try to connect to each potential relay.
for (let i = 0; i < diffRelayPeers.length; i++) {
const thisPeer = diffRelayPeers[i]
await useCases.relays.addRelay(thisPeer, thisNode)
}
// console.log('Exiting searchForRelays() Controller.')
} catch (err) {
// console.error('Error in timer-controller.js/searchForRelays(): ', err)
this.adapters.log.statusLog(
2,
'Error in timer-controller.js/searchForRelays(): ',
err
)
// Note: Do not throw an error. This is a top-level function.
}
// Periodically try to connect to problematic peers that advertise as
// potential circuit relays.
this.relaySearch = setInterval(async function () {
await _this.searchForRelays(thisNode, useCases)
}, 5 * 60000)
}
}
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
module.exports = TimerControllers