helia-coord 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.on-save.json +8 -0
- package/PEDIGREE.md +3 -0
- package/README.md +3 -0
- package/config/bootstrap-circuit-relays.js +48 -0
- package/examples/start-external.js +56 -0
- package/index.js +129 -0
- package/lib/adapters/bch-adapter.js +94 -0
- package/lib/adapters/encryption-adapter.js +123 -0
- package/lib/adapters/gist.js +42 -0
- package/lib/adapters/index.js +66 -0
- package/lib/adapters/ipfs-adapter.js +263 -0
- package/lib/adapters/logs-adapter.js +44 -0
- package/lib/adapters/pubsub-adapter/README.md +86 -0
- package/lib/adapters/pubsub-adapter/about-adapter.js +117 -0
- package/lib/adapters/pubsub-adapter/index.js +275 -0
- package/lib/adapters/pubsub-adapter/messaging.js +389 -0
- package/lib/adapters/pubsub-adapter/msg-router.js +58 -0
- package/lib/adapters/pubsub-adapter/resend-msg.js +58 -0
- package/lib/controllers/index.js +24 -0
- package/lib/controllers/timer-controller.js +417 -0
- package/lib/entities/this-node-entity.js +102 -0
- package/lib/use-cases/index.js +36 -0
- package/lib/use-cases/peer-use-cases.js +146 -0
- package/lib/use-cases/pubsub-use-cases.js +56 -0
- package/lib/use-cases/relay-use-cases.js +479 -0
- package/lib/use-cases/schema.js +158 -0
- package/lib/use-cases/this-node-use-cases.js +443 -0
- package/lib/util/utils.js +12 -0
- package/package.json +52 -0
- package/test/mocks/adapter-mock.js +119 -0
- package/test/mocks/circuit-relay-mocks.js +67 -0
- package/test/mocks/ipfs-mock.js +46 -0
- package/test/mocks/peers-mock.js +75 -0
- package/test/mocks/pubsub-mocks.js +37 -0
- package/test/mocks/thisnode-mocks.js +82 -0
- package/test/mocks/use-case-mocks.js +24 -0
- package/test/unit/adapters/bch-adapter-unit.js +96 -0
- package/test/unit/adapters/encryption-adapter-unit.js +129 -0
- package/test/unit/adapters/gist.unit.adapters.js +58 -0
- package/test/unit/adapters/index-adapters-unit.js +79 -0
- package/test/unit/adapters/ipfs-adapter-unit.js +215 -0
- package/test/unit/adapters/logs-adapter-unit.js +55 -0
- package/test/unit/adapters/pubsub/about-adapter-unit.js +129 -0
- package/test/unit/adapters/pubsub/messaging-adapter-unit.js +576 -0
- package/test/unit/adapters/pubsub/pubsub-adapter-unit.js +367 -0
- package/test/unit/adapters/pubsub/resend-msg-adapter-unit.js +58 -0
- package/test/unit/controllers/controllers-index-unit.js +30 -0
- package/test/unit/controllers/timer-controller-unit.js +261 -0
- package/test/unit/entities/this-node.unit.entity.js +157 -0
- package/test/unit/index-unit.js +160 -0
- package/test/unit/use-cases/peer.unit.use-cases.js +186 -0
- package/test/unit/use-cases/pubsub.unit.use-cases.js +114 -0
- package/test/unit/use-cases/relay-use-cases-unit.js +658 -0
- package/test/unit/use-cases/schema-use-case-unit.js +101 -0
- package/test/unit/use-cases/this-node-use-cases-unit.js +427 -0
- package/test/unit/use-cases/use-cases-index-unit.js +47 -0
- package/test/unit/util/utils-unit.js +31 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Use cases for interacting with subnet peer nodes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
class PeerUseCases {
|
|
6
|
+
constructor (localConfig = {}) {
|
|
7
|
+
// Dependency Injection.
|
|
8
|
+
this.adapters = localConfig.adapters
|
|
9
|
+
if (!this.adapters) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
'Must inject instance of adapters when instantiating Peer Use Cases library.'
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Connect to a peer through available circuit relays. This ensures a short
|
|
17
|
+
// path between peers, *before* broadcasting the OrbitDB message to them.
|
|
18
|
+
// This method is primarily used by sendPrivateMessage() to allow for fast-
|
|
19
|
+
// startup connection and communication with peers.
|
|
20
|
+
async connectToPeer (peerId, thisNode) {
|
|
21
|
+
try {
|
|
22
|
+
// console.log(`connectToPeer() called on ${peerId}`)
|
|
23
|
+
|
|
24
|
+
const relays = thisNode.relayData
|
|
25
|
+
|
|
26
|
+
// Get connected peers
|
|
27
|
+
const connectedPeers = await this.adapters.ipfs.getPeers()
|
|
28
|
+
|
|
29
|
+
// Check if target peer is currently conected to the node.
|
|
30
|
+
const connectedPeer = connectedPeers.filter(
|
|
31
|
+
peerObj => peerObj.peer === peerId
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// If this node is already connected to the peer, then return.
|
|
35
|
+
// We do not need to do anything.
|
|
36
|
+
if (connectedPeer.length) {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Sort the Circuit Relays by the average of the aboutLatency
|
|
41
|
+
// array. Connect to peers through the Relays with the lowest latencies
|
|
42
|
+
// first.
|
|
43
|
+
const sortedRelays = thisNode.useCases.relays.sortRelays(relays)
|
|
44
|
+
// console.log(`sortedRelays: ${JSON.stringify(sortedRelays, null, 2)}`)
|
|
45
|
+
|
|
46
|
+
// Loop through each known circuit relay and attempt to connect to the
|
|
47
|
+
// peer through a relay.
|
|
48
|
+
for (let i = 0; i < sortedRelays.length; i++) {
|
|
49
|
+
const thisRelay = sortedRelays[i]
|
|
50
|
+
// console.log(`thisRelay: ${JSON.stringify(thisRelay, null, 2)}`)
|
|
51
|
+
|
|
52
|
+
// Generate a multiaddr for connecting to the peer through a circuit relay.
|
|
53
|
+
const multiaddr = `${thisRelay.multiaddr}/p2p-circuit/p2p/${peerId}`
|
|
54
|
+
// console.log(`multiaddr: ${multiaddr}`)
|
|
55
|
+
|
|
56
|
+
// Skip the relay if this node is not connected to it.
|
|
57
|
+
if (thisRelay.connected) {
|
|
58
|
+
// Attempt to connect to the node through a circuit relay.
|
|
59
|
+
const connected = await this.adapters.ipfs.connectToPeer(multiaddr)
|
|
60
|
+
|
|
61
|
+
// If the connection was successful, break out of the relay loop.
|
|
62
|
+
// Otherwise try to connect through the next relay.
|
|
63
|
+
if (connected) {
|
|
64
|
+
// Exit once we've made a successful connection.
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Return false to indicate connection was unsuccessful.
|
|
71
|
+
return false
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('Error in peer-use-cases.js/connectToPeer()')
|
|
74
|
+
throw err
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Publish a string of text to another peers OrbitDB recieve database.
|
|
79
|
+
// orbitdbId input is optional.
|
|
80
|
+
async sendPrivateMessage (peerId, str, thisNode) {
|
|
81
|
+
try {
|
|
82
|
+
// console.log('sendPrivateMessage() peerId: ', peerId)
|
|
83
|
+
// console.log('\nsendPrivateMessage() str: ', str)
|
|
84
|
+
|
|
85
|
+
// const peer = this.peers.state.peers[peerId]
|
|
86
|
+
// console.log('thisNode.peerData: ', thisNode.peerData)
|
|
87
|
+
const peerData = thisNode.peerData.filter(x => x.from === peerId)
|
|
88
|
+
// console.log(
|
|
89
|
+
// `sendPrivateMessage peerData: ${JSON.stringify(peerData, null, 2)}`
|
|
90
|
+
// )
|
|
91
|
+
|
|
92
|
+
// Throw an error if the peer matching the peerId is not found.
|
|
93
|
+
if (peerData.length === 0) {
|
|
94
|
+
throw new Error(`Data for peer ${peerId} not found.`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Encrypt the string with the peers public key.
|
|
98
|
+
const encryptedStr = await this.adapters.encryption.encryptMsg(
|
|
99
|
+
peerData[0],
|
|
100
|
+
str
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Publish the message to the peers pubsub channel.
|
|
104
|
+
await this.adapters.pubsub.messaging.sendMsg(
|
|
105
|
+
peerId,
|
|
106
|
+
encryptedStr,
|
|
107
|
+
thisNode
|
|
108
|
+
)
|
|
109
|
+
// console.log('--->Successfully published to pubsub channel<---')
|
|
110
|
+
|
|
111
|
+
// const now = new Date()
|
|
112
|
+
//
|
|
113
|
+
// const peerDb = thisNode.orbitData.filter(x => x.ipfsId === peerId)
|
|
114
|
+
// // console.log('peerDb: ', peerDb)
|
|
115
|
+
//
|
|
116
|
+
// // Throw an error if peer database was not found.
|
|
117
|
+
// if (peerDb.length === 0) {
|
|
118
|
+
// throw new Error(`OrbitDB for peer ${peerId} not found.`)
|
|
119
|
+
// }
|
|
120
|
+
//
|
|
121
|
+
// // Connect to peer through Circuit Relay, using connectToPeer()
|
|
122
|
+
// // Note: isConnected will resolve to false if this node can not connect to
|
|
123
|
+
// // the peer. It will resolve to true if it successfully connected.
|
|
124
|
+
// await this.connectToPeer(peerId, thisNode)
|
|
125
|
+
//
|
|
126
|
+
// const db = peerDb[0].db
|
|
127
|
+
//
|
|
128
|
+
// const dbObj = {
|
|
129
|
+
// from: thisNode.ipfsId,
|
|
130
|
+
// data: encryptedStr,
|
|
131
|
+
// timestamp: now.toISOString()
|
|
132
|
+
// }
|
|
133
|
+
// // console.log(`dbObj: ${JSON.stringify(dbObj, null, 2)}`)
|
|
134
|
+
//
|
|
135
|
+
// // Add the encrypted message to the peers OrbitDB.
|
|
136
|
+
// await db.add(dbObj)
|
|
137
|
+
|
|
138
|
+
return true
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error('Error in peer-use-cases.js/sendPrivateMessage(): ', err)
|
|
141
|
+
throw err
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default PeerUseCases
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A Use Case library for interacting with the Pubsub Entity.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const DEFAULT_COORDINATION_ROOM = 'psf-ipfs-coordination-002'
|
|
6
|
+
const BCH_COINJOIN_ROOM = 'bch-coinjoin-001'
|
|
7
|
+
|
|
8
|
+
class PubsubUseCase {
|
|
9
|
+
constructor (localConfig = {}) {
|
|
10
|
+
// Dependency Injection.
|
|
11
|
+
this.adapters = localConfig.adapters
|
|
12
|
+
if (!this.adapters) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'Must inject instance of adapters when instantiating Pubsub Use Cases library.'
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
this.thisNodeUseCases = localConfig.thisNodeUseCases
|
|
18
|
+
if (!this.thisNodeUseCases) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'thisNode use cases required when instantiating Pubsub Use Cases library.'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Allow the app to override the default CoinJoin pubsub handler.
|
|
25
|
+
this.coinjoinPubsubHandler = () => true
|
|
26
|
+
if (localConfig.coinjoinPubsubHandler) this.coinjoinPubsubHandler = localConfig.coinjoinPubsubHandler
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Connect to the default pubsub rooms.
|
|
30
|
+
async initializePubsub (thisNode) {
|
|
31
|
+
try {
|
|
32
|
+
// Subscribe to the coordination channel, where new peers announce themselves
|
|
33
|
+
// to the network.
|
|
34
|
+
await this.adapters.pubsub.subscribeToPubsubChannel(
|
|
35
|
+
DEFAULT_COORDINATION_ROOM,
|
|
36
|
+
// this.adapters.peers.addPeer
|
|
37
|
+
this.thisNodeUseCases.addSubnetPeer,
|
|
38
|
+
thisNode
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// Subscribe to the BCH CoinJoin coordination channel. This code is here
|
|
42
|
+
// so that Circuit Relays automatically subscribe to the channel and
|
|
43
|
+
// relay the messages.
|
|
44
|
+
await this.adapters.pubsub.subscribeToPubsubChannel(
|
|
45
|
+
BCH_COINJOIN_ROOM,
|
|
46
|
+
this.coinjoinPubsubHandler,
|
|
47
|
+
thisNode
|
|
48
|
+
)
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('Error in pubsub-use-cases.js/initializePubsub()')
|
|
51
|
+
throw err
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default PubsubUseCase
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A Use Case library for working with Circuit Relays.
|
|
3
|
+
|
|
4
|
+
TODO: Build these methods:
|
|
5
|
+
- pruneConnections() - Disconnect dead or misbehaving nodes.
|
|
6
|
+
- addRelay() - Add a new relay to the state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import bootstrapCircuitRelays from '../../config/bootstrap-circuit-relays.js'
|
|
10
|
+
// const RelayEntity = require('../entities/relay-entity')
|
|
11
|
+
|
|
12
|
+
class RelayUseCases {
|
|
13
|
+
constructor (localConfig = {}) {
|
|
14
|
+
// Dependency Injection.
|
|
15
|
+
this.adapters = localConfig.adapters
|
|
16
|
+
if (!this.adapters) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'Must inject instance of adapters when instantiating Relay Use Cases library.'
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
// this.controllers = localConfig.controllers
|
|
22
|
+
// if (!this.controllers) {
|
|
23
|
+
// throw new Error(
|
|
24
|
+
// 'Must inject instance of controllers when instantiating Relay Use Cases library.'
|
|
25
|
+
// )
|
|
26
|
+
// }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Connect to the pre-programmed circuit relays for the first time at startup.
|
|
30
|
+
async initializeRelays (thisNode) {
|
|
31
|
+
try {
|
|
32
|
+
// Set the initial CR bootstrap nodes based on the type of IPFS node.
|
|
33
|
+
let bootstrapRelays = []
|
|
34
|
+
if (thisNode.type === 'browser') {
|
|
35
|
+
bootstrapRelays = bootstrapCircuitRelays.browser
|
|
36
|
+
} else {
|
|
37
|
+
bootstrapRelays = bootstrapCircuitRelays.node
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Loop through each bootstrap Relay.
|
|
41
|
+
for (let i = 0; i < bootstrapRelays.length; i++) {
|
|
42
|
+
const thisRelay = bootstrapRelays[i]
|
|
43
|
+
|
|
44
|
+
// Attempt to connect to the Circuit Relay peer.
|
|
45
|
+
const connectionStatus = await this.adapters.ipfs.connectToPeer(
|
|
46
|
+
thisRelay.multiaddr
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const now = new Date()
|
|
50
|
+
|
|
51
|
+
const newRelayObj = {
|
|
52
|
+
multiaddr: thisRelay.multiaddr,
|
|
53
|
+
connected: connectionStatus,
|
|
54
|
+
updatedAt: now,
|
|
55
|
+
ipfsId: thisRelay.ipfsId,
|
|
56
|
+
isBootstrap: true,
|
|
57
|
+
metrics: {
|
|
58
|
+
aboutLatency: []
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Record relay information in thisNode entity.
|
|
63
|
+
thisNode.relayData.push(newRelayObj)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error('Error in relay-use-case.js/initializeRelays()')
|
|
69
|
+
throw err
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Download a list of Circuit Relays from a GitHub Gist, and try to connect
|
|
74
|
+
// to all of the ones in the list.
|
|
75
|
+
async getCRGist (thisNode) {
|
|
76
|
+
try {
|
|
77
|
+
const gistCRs = await this.adapters.gist.getCRList()
|
|
78
|
+
|
|
79
|
+
let gistRelays
|
|
80
|
+
if (thisNode.type === 'browser') {
|
|
81
|
+
gistRelays = gistCRs.browser
|
|
82
|
+
} else {
|
|
83
|
+
gistRelays = gistCRs.node
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`Relays from the gist: ${JSON.stringify(gistRelays, null, 2)}`)
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < gistRelays.length; i++) {
|
|
89
|
+
const thisRelay = gistRelays[i]
|
|
90
|
+
|
|
91
|
+
// If this relay is already in the list of circuit relays, then skip.
|
|
92
|
+
const alreadyExists = thisNode.relayData.filter(
|
|
93
|
+
x => x.ipfsId === thisRelay.ipfsId
|
|
94
|
+
)
|
|
95
|
+
if (alreadyExists.length) continue
|
|
96
|
+
|
|
97
|
+
// Attempt to connect to the Circuit Relay peer.
|
|
98
|
+
console.log(`getCRGist() Connecting to Circuit Relay ${thisRelay.multiaddr}`)
|
|
99
|
+
const connectionStatus = await this.adapters.ipfs.connectToPeer(
|
|
100
|
+
thisRelay.multiaddr
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const now = new Date()
|
|
104
|
+
|
|
105
|
+
const newRelayObj = {
|
|
106
|
+
multiaddr: thisRelay.multiaddr,
|
|
107
|
+
connected: connectionStatus,
|
|
108
|
+
updatedAt: now,
|
|
109
|
+
ipfsId: thisRelay.ipfsId,
|
|
110
|
+
isBootstrap: false,
|
|
111
|
+
metrics: {
|
|
112
|
+
aboutLatency: []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Record relay information in thisNode entity.
|
|
117
|
+
thisNode.relayData.push(newRelayObj)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Remove duplicate Circuit Relays.
|
|
121
|
+
await this.removeDuplicates(thisNode)
|
|
122
|
+
|
|
123
|
+
return true
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error('Error in relay-use-case.js/getCRGist()')
|
|
126
|
+
throw err
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Renew the connection to each circuit relay in the state.
|
|
131
|
+
async connectToCRs (thisNode) {
|
|
132
|
+
try {
|
|
133
|
+
let knownRelays = thisNode.relayData
|
|
134
|
+
|
|
135
|
+
// Sort the Relays by their latency. This way Relays with the lowest latency
|
|
136
|
+
// (relative to thisNode) are used first.
|
|
137
|
+
knownRelays = this.sortRelays(knownRelays)
|
|
138
|
+
// console.log('knownRelays: ', knownRelays)
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < knownRelays.length; i++) {
|
|
141
|
+
const thisRelay = knownRelays[i]
|
|
142
|
+
// console.log('thisRelay: ', thisRelay)
|
|
143
|
+
|
|
144
|
+
// Get connected peers
|
|
145
|
+
const connectedPeers = await this.adapters.ipfs.getPeers()
|
|
146
|
+
// console.log('connectedPeers: ', connectedPeers)
|
|
147
|
+
|
|
148
|
+
// Check if target circuit-relay is currently conected to the node.
|
|
149
|
+
const connectedPeer = connectedPeers.filter(peerObj => thisRelay.multiaddr.match(peerObj.peer)
|
|
150
|
+
)
|
|
151
|
+
// console.log('connectedPeer: ', connectedPeer)
|
|
152
|
+
|
|
153
|
+
// Update the connection state.
|
|
154
|
+
|
|
155
|
+
if (connectedPeer.length) {
|
|
156
|
+
// Already connected.
|
|
157
|
+
thisRelay.connected = true
|
|
158
|
+
console.log(`Already connected to Circuit Relay ${thisRelay.multiaddr}`)
|
|
159
|
+
} else {
|
|
160
|
+
// If this node is not connected, try to connect again.
|
|
161
|
+
console.log(`Connecting to Circuit Relay ${thisRelay.multiaddr}`)
|
|
162
|
+
thisRelay.connected = await this.adapters.ipfs.connectToPeer(
|
|
163
|
+
thisRelay.multiaddr
|
|
164
|
+
)
|
|
165
|
+
console.log(`thisRelay.connected: ${thisRelay.connected}`)
|
|
166
|
+
|
|
167
|
+
if (thisRelay.connected) {
|
|
168
|
+
this.adapters.log.statusLog(
|
|
169
|
+
0,
|
|
170
|
+
`Connected to Circuit Relay peer ${thisRelay.ipfsId}`
|
|
171
|
+
)
|
|
172
|
+
console.log(`connectToCRs() Connected to Circuit Relay peer ${thisRelay.ipfsId}`)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// console.log('thisRelay.connected: ', thisRelay.connected)
|
|
176
|
+
|
|
177
|
+
// Update the timestamp.
|
|
178
|
+
const now = new Date()
|
|
179
|
+
thisRelay.updatedAt = now
|
|
180
|
+
|
|
181
|
+
// Update the state tracked by This Node.
|
|
182
|
+
thisNode.relayData[i] = thisRelay
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// const now = new Date()
|
|
186
|
+
// this.adapters.log.statusLog(
|
|
187
|
+
// `status: ${now.toLocaleString()}: Renewed connections to all known Circuit Relay nodes.`
|
|
188
|
+
// )
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.log('Error in connectToCRs()')
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Sort the relay data relative to the measured latency metrics.
|
|
196
|
+
sortRelays (relayData) {
|
|
197
|
+
try {
|
|
198
|
+
// Loop through each element and add a latency score.
|
|
199
|
+
for (let i = 0; i < relayData.length; i++) {
|
|
200
|
+
const thisRelay = relayData[i]
|
|
201
|
+
|
|
202
|
+
// Ensure an initial default value is assigned.
|
|
203
|
+
thisRelay.latencyScore = 10000
|
|
204
|
+
|
|
205
|
+
// Assign bootstrap nodes the maximum latency.
|
|
206
|
+
if (thisRelay.isBootstrap) {
|
|
207
|
+
thisRelay.latencyScore = 10000
|
|
208
|
+
} else {
|
|
209
|
+
// Average the latencies measured for this Relay.
|
|
210
|
+
thisRelay.latencyScore = Math.floor(
|
|
211
|
+
this._average(thisRelay.metrics.aboutLatency)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
// Handle corner-case of empty arrays.
|
|
215
|
+
if (isNaN(thisRelay.latencyScore)) thisRelay.latencyScore = 10000
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// console.log('thisRelay: ', thisRelay)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Sort the array by the latency score.
|
|
222
|
+
// https://flaviocopes.com/how-to-sort-array-of-objects-by-property-javascript/
|
|
223
|
+
const sortedList = relayData.sort((a, b) => a.latencyScore > b.latencyScore ? 1 : -1
|
|
224
|
+
)
|
|
225
|
+
// console.log('Relay sortedList: ', sortedList)
|
|
226
|
+
|
|
227
|
+
return sortedList
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.error('Error in sortRelays()')
|
|
230
|
+
throw err
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Average together the elements of an array.
|
|
235
|
+
_average (arr) {
|
|
236
|
+
return arr.reduce((p, c) => p + c, 0) / arr.length
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If a subnet Peer has the isCircuitRelay flag set, try to connect to them
|
|
240
|
+
// directly. If the connection succeeds, add them to the list of Circuit Relays.
|
|
241
|
+
// Triggered when a new subnet peer is found that has their Relay flag set.
|
|
242
|
+
async addRelay (ipfsId, thisNode) {
|
|
243
|
+
try {
|
|
244
|
+
const now = new Date()
|
|
245
|
+
// console.log(`debug: addRelay() called at ${now.toLocaleString()}`)
|
|
246
|
+
|
|
247
|
+
// Check to see if peer is already in the list of Circuit Relays.
|
|
248
|
+
const alreadyInList = thisNode.relayData.filter(x => x.ipfsId.includes(ipfsId)
|
|
249
|
+
)
|
|
250
|
+
// Exit if this peer is already in the list of Relays.
|
|
251
|
+
if (alreadyInList.length) return true
|
|
252
|
+
|
|
253
|
+
// this.adapters.log.statusLog(1, 'New Circuit Relay Peer Found!')
|
|
254
|
+
|
|
255
|
+
// Get the peer data.
|
|
256
|
+
let peerData = thisNode.peerData.filter(x => x.data.ipfsId === ipfsId)
|
|
257
|
+
peerData = peerData[0]
|
|
258
|
+
// console.log('peerData: ', peerData)
|
|
259
|
+
|
|
260
|
+
// Exit if this peer is not a circuit relay.
|
|
261
|
+
// CT Added 9/4/21.
|
|
262
|
+
// Added because testing was showing that some non-relay peers where getting
|
|
263
|
+
// pings with about metrics.
|
|
264
|
+
const isCircuitRelay = peerData.data.isCircuitRelay
|
|
265
|
+
if (!isCircuitRelay) return false // Return true?
|
|
266
|
+
|
|
267
|
+
this.adapters.log.statusLog(
|
|
268
|
+
1,
|
|
269
|
+
`New Circuit Relay Peer Found: ${peerData.from}`
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
// Grab the optional connection information if the Circuit Relay provides
|
|
273
|
+
// it. If they do, construct a connection string and add it to the
|
|
274
|
+
// list of multiaddrs.
|
|
275
|
+
const circuitRelayInfo = peerData.data.circuitRelayInfo
|
|
276
|
+
if (circuitRelayInfo) {
|
|
277
|
+
if (circuitRelayInfo.ip4) {
|
|
278
|
+
const ip4TcpMultiaddr = `/ip4/${circuitRelayInfo.ip4}/tcp/${circuitRelayInfo.tcpPort}/p2p/${peerData.from}`
|
|
279
|
+
peerData.data.ipfsMultiaddrs.push(ip4TcpMultiaddr)
|
|
280
|
+
}
|
|
281
|
+
if (circuitRelayInfo.crDomain) {
|
|
282
|
+
const dnsMultiaddr = `/dns4/${circuitRelayInfo.crDomain}/tcp/443/wss/ipfs/${peerData.from}`
|
|
283
|
+
peerData.data.ipfsMultiaddrs.push(dnsMultiaddr)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Debugging
|
|
287
|
+
// console.log(
|
|
288
|
+
// `peer multiaddrs: ${JSON.stringify(
|
|
289
|
+
// peerData.data.ipfsMultiaddrs,
|
|
290
|
+
// null,
|
|
291
|
+
// 2
|
|
292
|
+
// )}`
|
|
293
|
+
// )
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let multiaddr = ''
|
|
297
|
+
let connectSuccess = false
|
|
298
|
+
|
|
299
|
+
// Try to connect to one of the multiaddrs.
|
|
300
|
+
for (let i = 0; i < peerData.data.ipfsMultiaddrs.length; i++) {
|
|
301
|
+
const thisAddr = peerData.data.ipfsMultiaddrs[i]
|
|
302
|
+
// console.log('thisAddr: ', thisAddr)
|
|
303
|
+
|
|
304
|
+
// Try to connect to the IPFS peer.
|
|
305
|
+
connectSuccess = await this.adapters.ipfs.connectToPeer(thisAddr)
|
|
306
|
+
|
|
307
|
+
// If connection was successful
|
|
308
|
+
if (connectSuccess) {
|
|
309
|
+
// Get a list of connect peers.
|
|
310
|
+
const peers = await this.adapters.ipfs.getPeers()
|
|
311
|
+
// console.log('addRelay() peers: ', peers)
|
|
312
|
+
|
|
313
|
+
// Retrieve the multiaddr that worked for connecting.
|
|
314
|
+
const thisRelay = peers.filter(x => x.peer.toString() === ipfsId)
|
|
315
|
+
// console.log('thisRelay: ', thisRelay)
|
|
316
|
+
multiaddr = thisRelay[0].addr.toString()
|
|
317
|
+
// console.log('multiaddr: ', multiaddr)
|
|
318
|
+
|
|
319
|
+
// If multiaddr does not contain ip4, ip6, or dns, then reject it,
|
|
320
|
+
// because the connection is happening through another Relay.
|
|
321
|
+
// If the multiaddr has 'p2p-circuit' in it, then it's also happening
|
|
322
|
+
// through a Relay and should be rejected.
|
|
323
|
+
const isIp4 = multiaddr.includes('ip4')
|
|
324
|
+
const isIp6 = multiaddr.includes('ip6')
|
|
325
|
+
const isDns = multiaddr.includes('dns')
|
|
326
|
+
const isBridged = multiaddr.includes('p2p-circuit')
|
|
327
|
+
if ((!isIp4 && !isIp6 && !isDns) || isBridged) {
|
|
328
|
+
this.adapters.log.statusLog(
|
|
329
|
+
1,
|
|
330
|
+
`Rejecting Relay multiaddr ${multiaddr}`
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
// Disconnect from the peer, so that a direct connection can be
|
|
334
|
+
// attempted.
|
|
335
|
+
await this.adapters.ipfs.disconnectFromPeer(ipfsId)
|
|
336
|
+
|
|
337
|
+
connectSuccess = false
|
|
338
|
+
continue
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
break
|
|
342
|
+
} else {
|
|
343
|
+
this.adapters.log.statusLog(
|
|
344
|
+
1,
|
|
345
|
+
`Connecting to potential relay did not succeed with multiaddr ${thisAddr}`
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// If we could not successfully connect to the peer with any of the given
|
|
351
|
+
// multiaddrs, then do not add it to the list of Relays.
|
|
352
|
+
if (!connectSuccess) {
|
|
353
|
+
this.adapters.log.statusLog(
|
|
354
|
+
1,
|
|
355
|
+
`: Could not add potential Circuit Relay: ${ipfsId}`
|
|
356
|
+
)
|
|
357
|
+
return false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Add tail end of multiaddr if it's omitted. (this happens in go-ipfs v0.11.0)
|
|
361
|
+
if (!multiaddr.includes(ipfsId)) {
|
|
362
|
+
multiaddr = `${multiaddr}/p2p/${ipfsId}`
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// On successful connection, add the Circuit Relay to the list.
|
|
366
|
+
// const now = new Date()
|
|
367
|
+
const newRelayObj = {
|
|
368
|
+
multiaddr,
|
|
369
|
+
connected: true,
|
|
370
|
+
updatedAt: now,
|
|
371
|
+
ipfsId,
|
|
372
|
+
isBootstrap: false,
|
|
373
|
+
metrics: {
|
|
374
|
+
aboutLatency: []
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// console.log(`newRelayObj: ${JSON.stringify(newRelayObj, null, 2)}`)
|
|
378
|
+
thisNode.relayData.push(newRelayObj)
|
|
379
|
+
|
|
380
|
+
console.log(`New Circuit Relay added: ${multiaddr}`)
|
|
381
|
+
|
|
382
|
+
return true
|
|
383
|
+
} catch (err) {
|
|
384
|
+
console.error('Error in addRelay(): ', err)
|
|
385
|
+
// top-level function. Do not throw an error.
|
|
386
|
+
return false
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Called when periodically checking the Relay connection. Maintains network
|
|
391
|
+
// metrics about each Relay.
|
|
392
|
+
// Called by the manageCircuitRelays() Timer Controller.
|
|
393
|
+
async measureRelays (thisNode) {
|
|
394
|
+
try {
|
|
395
|
+
// console.log('Entering measureRelays()')
|
|
396
|
+
// console.log('thisNode.relayData: ', thisNode.relayData)
|
|
397
|
+
// console.log(
|
|
398
|
+
// `thisNode.peerData: ${JSON.stringify(thisNode.peerData, null, 2)}`
|
|
399
|
+
// )
|
|
400
|
+
|
|
401
|
+
// Loop through each Circuit Relay
|
|
402
|
+
for (let i = 0; i < thisNode.relayData.length; i++) {
|
|
403
|
+
const thisRelay = thisNode.relayData[i]
|
|
404
|
+
|
|
405
|
+
// If the relay is from the bootstrap list, skip it.
|
|
406
|
+
// if (thisRelay.isBootstrap) continue
|
|
407
|
+
|
|
408
|
+
// Get the peer data.
|
|
409
|
+
let peerData = thisNode.peerData.filter(x => {
|
|
410
|
+
// console.log(`x.data.ipfsId: ${x.data.ipfsId}`)
|
|
411
|
+
// console.log(`thisRelay.ipfsId: ${thisRelay.ipfsId}`)
|
|
412
|
+
return x.data.ipfsId === thisRelay.ipfsId
|
|
413
|
+
})
|
|
414
|
+
peerData = peerData[0]
|
|
415
|
+
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
|
416
|
+
|
|
417
|
+
// Skip if this Relay is not advertising as a relay.
|
|
418
|
+
if (!peerData || !peerData.data.isCircuitRelay) continue
|
|
419
|
+
|
|
420
|
+
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
|
421
|
+
|
|
422
|
+
// If this node is not connected to the Relay, give it the worst score.
|
|
423
|
+
if (!thisRelay.connected) {
|
|
424
|
+
thisRelay.metrics.aboutLatency.push(10000)
|
|
425
|
+
continue
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Poll the /about JSON endpoint, and track the time it takes to recieve
|
|
429
|
+
// a response.
|
|
430
|
+
const startTime = new Date()
|
|
431
|
+
const testResult = await this.adapters.pubsub.about.queryAbout(
|
|
432
|
+
thisRelay.ipfsId,
|
|
433
|
+
thisNode
|
|
434
|
+
)
|
|
435
|
+
// await this.adapters.bch.bchjs.Util.sleep(2000)
|
|
436
|
+
const endTime = new Date()
|
|
437
|
+
|
|
438
|
+
let diffTime = 10000
|
|
439
|
+
if (testResult) diffTime = endTime.getTime() - startTime.getTime()
|
|
440
|
+
// console.log(`Time difference: ${diffTime} mS`)
|
|
441
|
+
|
|
442
|
+
// Prune the metrics array if it's too big.
|
|
443
|
+
if (thisRelay.metrics.aboutLatency.length > 9) {
|
|
444
|
+
thisRelay.metrics.aboutLatency.shift()
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Save the new metric value.
|
|
448
|
+
thisRelay.metrics.aboutLatency.push(diffTime)
|
|
449
|
+
}
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.error('Error in measureRelays(): ', err)
|
|
452
|
+
throw err
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Remove any duplicate entries of Circuit Relays.
|
|
457
|
+
// This is middleware that edits the thisNode.relayData in place.
|
|
458
|
+
removeDuplicates (thisNode) {
|
|
459
|
+
try {
|
|
460
|
+
const startingRelays = thisNode.relayData
|
|
461
|
+
// console.log('thisNode.relayData: ', thisNode.relayData)
|
|
462
|
+
|
|
463
|
+
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
|
|
464
|
+
const endingRelays = startingRelays.filter(
|
|
465
|
+
(relay, index, self) => index === self.findIndex(t => t.ipfsId === relay.ipfsId)
|
|
466
|
+
)
|
|
467
|
+
// console.log('endingRelays: ', endingRelays)
|
|
468
|
+
|
|
469
|
+
thisNode.relayData = endingRelays
|
|
470
|
+
|
|
471
|
+
return true
|
|
472
|
+
} catch (err) {
|
|
473
|
+
console.error('Error in removeDuplicates()')
|
|
474
|
+
throw err
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export default RelayUseCases
|