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,263 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Adapter library for IPFS, so the rest of the business logic doesn't need to
|
|
3
|
+
know specifics about the IPFS API.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// The amount of time to wait to connect to a peer, in milliseconds.
|
|
7
|
+
// Increasing the time makes the network slower but more resilient to latency.
|
|
8
|
+
// Decreasing the time makes the network faster, but more smaller and more fragile.
|
|
9
|
+
// import bootstapNodes from '../../config/bootstrap-circuit-relays.js'
|
|
10
|
+
import { multiaddr } from '@multiformats/multiaddr'
|
|
11
|
+
|
|
12
|
+
const CONNECTION_TIMEOUT = 10000
|
|
13
|
+
|
|
14
|
+
class IpfsAdapter {
|
|
15
|
+
constructor (localConfig = {}) {
|
|
16
|
+
// Input Validation
|
|
17
|
+
this.ipfs = localConfig.ipfs
|
|
18
|
+
if (!this.ipfs) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'An instance of IPFS must be passed when instantiating the IPFS adapter library.'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
this.log = localConfig.log
|
|
24
|
+
if (!this.log) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'A status log handler must be specified when instantiating IPFS adapter library.'
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 'embedded' node type used as default, will use embedded js-ipfs.
|
|
31
|
+
// Alternative is 'external' which will use ipfs-http-client to control an
|
|
32
|
+
// external IPFS node.
|
|
33
|
+
this.nodeType = localConfig.nodeType
|
|
34
|
+
if (!this.nodeType) {
|
|
35
|
+
// console.log('No node type specified. Assuming embedded js-ipfs.')
|
|
36
|
+
this.nodeType = 'embedded'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Port Settings. Defaults are overwritten if specified in the localConfig.
|
|
40
|
+
this.tcpPort = 4001
|
|
41
|
+
if (localConfig.tcpPort) this.tcpPort = localConfig.tcpPort
|
|
42
|
+
this.wsPort = 4003
|
|
43
|
+
if (localConfig.wsPort) this.wsPort = localConfig.wsPort
|
|
44
|
+
|
|
45
|
+
// Placeholders that will be filled in after the node finishes initializing.
|
|
46
|
+
this.ipfsPeerId = ''
|
|
47
|
+
this.ipfsMultiaddrs = ''
|
|
48
|
+
|
|
49
|
+
// Encapsulate dependencies
|
|
50
|
+
this.multiaddr = multiaddr
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Start the IPFS node if it hasn't already been started.
|
|
54
|
+
// Update the state of this adapter with the IPFS node information.
|
|
55
|
+
async start () {
|
|
56
|
+
try {
|
|
57
|
+
// Wait until the IPFS creation Promise has resolved, and the node is
|
|
58
|
+
// fully instantiated.
|
|
59
|
+
this.ipfs = await this.ipfs
|
|
60
|
+
|
|
61
|
+
// Get ID information about this IPFS node.
|
|
62
|
+
// const id2 = await this.ipfs.id()
|
|
63
|
+
// this.state.ipfsPeerId = id2.id
|
|
64
|
+
// this.ipfsPeerId = id2.id
|
|
65
|
+
this.ipfsPeerId = this.ipfs.libp2p.peerId.toString()
|
|
66
|
+
|
|
67
|
+
// Get multiaddrs that can be used to connect to this node.
|
|
68
|
+
// const addrs = id2.addresses.map(elem => elem.toString())
|
|
69
|
+
const addrs = this.ipfs.libp2p.getMultiaddrs().map(elem => elem.toString())
|
|
70
|
+
// this.state.ipfsMultiaddrs = addrs
|
|
71
|
+
this.ipfsMultiaddrs = addrs
|
|
72
|
+
|
|
73
|
+
// Remove bootstrap nodes, as we have our own bootstrap nodes, and the
|
|
74
|
+
// the default ones can spam our nodes with a ton of bandwidth.
|
|
75
|
+
// await this.ipfs.config.set('Bootstrap', [
|
|
76
|
+
// bootstapNodes.node[0].multiaddr,
|
|
77
|
+
// bootstapNodes.node[1].multiaddr
|
|
78
|
+
// ])
|
|
79
|
+
|
|
80
|
+
// Settings specific to embedded js-ipfs.
|
|
81
|
+
// if (this.nodeType === 'embedded') {
|
|
82
|
+
// // Also remove default Delegates, as they are the same as the default
|
|
83
|
+
// // Bootstrap nodes.
|
|
84
|
+
// await this.ipfs.config.set('Addresses.Delegates', [])
|
|
85
|
+
// }
|
|
86
|
+
|
|
87
|
+
// Settings specific to external go-ipfs node.
|
|
88
|
+
// if (this.nodeType === 'external') {
|
|
89
|
+
// // Enable RelayClient
|
|
90
|
+
// // https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#swarmrelayclient
|
|
91
|
+
// // https://github.com/ipfs/go-ipfs/releases/tag/v0.11.0
|
|
92
|
+
// await this.ipfs.config.set('Swarm.RelayClient.Enabled', true)
|
|
93
|
+
|
|
94
|
+
// // Enable hole punching for better p2p interaction.
|
|
95
|
+
// // https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#swarmenableholepunching
|
|
96
|
+
// await this.ipfs.config.set('Swarm.EnableHolePunching', true)
|
|
97
|
+
|
|
98
|
+
// // Enable websocket connections
|
|
99
|
+
// await this.ipfs.config.set('Addresses.Swarm', [
|
|
100
|
+
// `/ip4/0.0.0.0/tcp/${this.tcpPort}`,
|
|
101
|
+
// `/ip6/::/tcp/${this.tcpPort}`,
|
|
102
|
+
// // `/ip4/0.0.0.0/udp/${this.tcpPort}/quic`,
|
|
103
|
+
// // `/ip6/::/udp/${this.tcpPort}/quic`,
|
|
104
|
+
// `/ip4/0.0.0.0/tcp/${this.wsPort}`, // Websockets
|
|
105
|
+
// `/ip6/::/tcp/${this.wsPort}`
|
|
106
|
+
// ])
|
|
107
|
+
|
|
108
|
+
// // Disable scanning of IP ranges. This is largely driven by Hetzner
|
|
109
|
+
// await this.ipfs.config.set('Swarm.AddrFilters', [
|
|
110
|
+
// '/ip4/10.0.0.0/ipcidr/8',
|
|
111
|
+
// '/ip4/100.0.0.0/ipcidr/8',
|
|
112
|
+
// '/ip4/169.254.0.0/ipcidr/16',
|
|
113
|
+
// '/ip4/172.16.0.0/ipcidr/12',
|
|
114
|
+
// '/ip4/192.0.0.0/ipcidr/24',
|
|
115
|
+
// '/ip4/192.0.2.0/ipcidr/24',
|
|
116
|
+
// '/ip4/192.168.0.0/ipcidr/16',
|
|
117
|
+
// '/ip4/198.18.0.0/ipcidr/15',
|
|
118
|
+
// '/ip4/198.51.100.0/ipcidr/24',
|
|
119
|
+
// '/ip4/203.0.113.0/ipcidr/24',
|
|
120
|
+
// '/ip4/240.0.0.0/ipcidr/4',
|
|
121
|
+
// '/ip6/100::/ipcidr/64',
|
|
122
|
+
// '/ip6/2001:2::/ipcidr/48',
|
|
123
|
+
// '/ip6/2001:db8::/ipcidr/32',
|
|
124
|
+
// '/ip6/fc00::/ipcidr/7',
|
|
125
|
+
// '/ip6/fe80::/ipcidr/10'
|
|
126
|
+
// ])
|
|
127
|
+
|
|
128
|
+
// // go-ipfs v0.10.0
|
|
129
|
+
// // await this.ipfs.config.set('Swarm.EnableRelayHop', true)
|
|
130
|
+
// // await this.ipfs.config.set('Swarm.EnableAutoRelay', true)
|
|
131
|
+
|
|
132
|
+
// // Disable peer discovery
|
|
133
|
+
// // await this.ipfs.config.set('Routing.Type', 'none')
|
|
134
|
+
// }
|
|
135
|
+
|
|
136
|
+
// Disable preloading
|
|
137
|
+
// await this.ipfs.config.set('preload.enabled', false)
|
|
138
|
+
|
|
139
|
+
// Reduce the default number of peers thisNode connects to at one time.
|
|
140
|
+
// await this.ipfs.config.set('Swarm.ConnMgr', {
|
|
141
|
+
// LowWater: 10,
|
|
142
|
+
// HighWater: 30,
|
|
143
|
+
// GracePeriod: '2s'
|
|
144
|
+
// })
|
|
145
|
+
|
|
146
|
+
// Reduce the storage size, as this node should not be retaining much data.
|
|
147
|
+
// await this.ipfs.config.set('Datastore.StorageMax', '2GB')
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error('Error in ipfs-adapter.js/start()')
|
|
150
|
+
throw err
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Attempts to connect to an IPFS peer, given its IPFS multiaddr.
|
|
155
|
+
// Returns true if the connection succeeded. Otherwise returns false.
|
|
156
|
+
async connectToPeer (ipfsAddr) {
|
|
157
|
+
try {
|
|
158
|
+
// TODO: Throw error if ipfs ID is passed, instead of a multiaddr.
|
|
159
|
+
console.log('ipfsAddr: ', ipfsAddr)
|
|
160
|
+
|
|
161
|
+
// await this.ipfs.swarm.connect(ipfsAddr, { timeout: CONNECTION_TIMEOUT })
|
|
162
|
+
await this.ipfs.libp2p.dial(this.multiaddr(ipfsAddr))
|
|
163
|
+
|
|
164
|
+
this.log.statusLog(1, `Successfully connected to peer node ${ipfsAddr}`)
|
|
165
|
+
|
|
166
|
+
return true
|
|
167
|
+
} catch (err) {
|
|
168
|
+
/* exit quietly */
|
|
169
|
+
console.warn(
|
|
170
|
+
`Error trying to connect to peer node ${ipfsAddr}: ${err.message}`
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// if (this.debugLevel === 1) {
|
|
174
|
+
// this.statusLog(
|
|
175
|
+
// `status: Error trying to connect to peer node ${ipfsAddr}`
|
|
176
|
+
// )
|
|
177
|
+
// } else if (this.debugLevel === 2) {
|
|
178
|
+
// this.statusLog(
|
|
179
|
+
// `status: Error trying to connect to peer node ${ipfsAddr}: `,
|
|
180
|
+
// err
|
|
181
|
+
// )
|
|
182
|
+
// }
|
|
183
|
+
this.log.statusLog(2, `Error trying to connect to peer node ${ipfsAddr}`)
|
|
184
|
+
// console.log(`Error trying to connect to peer node ${ipfsAddr}: `, err)
|
|
185
|
+
|
|
186
|
+
// this.log.statusLog(
|
|
187
|
+
// 3,
|
|
188
|
+
// `Error trying to connect to peer node ${ipfsAddr}: `,
|
|
189
|
+
// err
|
|
190
|
+
// )
|
|
191
|
+
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Disconnect from a peer.
|
|
197
|
+
async disconnectFromPeer (ipfsId) {
|
|
198
|
+
try {
|
|
199
|
+
// TODO: If given a multiaddr, extract the IPFS ID.
|
|
200
|
+
|
|
201
|
+
// Get the list of peers that we're connected to.
|
|
202
|
+
const connectedPeers = await this.getPeers()
|
|
203
|
+
// console.log('connectedPeers: ', connectedPeers)
|
|
204
|
+
|
|
205
|
+
// See if we're connected to the given IPFS ID
|
|
206
|
+
const connectedPeer = connectedPeers.filter(x => x.peer === ipfsId)
|
|
207
|
+
|
|
208
|
+
// If we're not connected, exit.
|
|
209
|
+
if (!connectedPeer.length) {
|
|
210
|
+
// console.log(`debug: Not connected to ${ipfsId}`)
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// If connected, disconnect from the peer.
|
|
215
|
+
await this.ipfs.swarm.disconnect(connectedPeer[0].addr, {
|
|
216
|
+
timeout: CONNECTION_TIMEOUT
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return true
|
|
220
|
+
} catch (err) {
|
|
221
|
+
// exit quietly
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async disconnectFromMultiaddr (multiaddr) {
|
|
227
|
+
try {
|
|
228
|
+
await this.ipfs.swarm.disconnect(multiaddr, {
|
|
229
|
+
timeout: CONNECTION_TIMEOUT
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
return true
|
|
233
|
+
} catch (err) {
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Get a list of all the IPFS peers This Node is connected to.
|
|
239
|
+
async getPeers () {
|
|
240
|
+
try {
|
|
241
|
+
// console.log('this.ipfs.libp2p: ', this.ipfs.libp2p)
|
|
242
|
+
|
|
243
|
+
// Get connected peers
|
|
244
|
+
// const connectedPeers = await this.ipfs.swarm.peers({
|
|
245
|
+
// direction: true,
|
|
246
|
+
// streams: true,
|
|
247
|
+
// verbose: true,
|
|
248
|
+
// latency: true
|
|
249
|
+
// })
|
|
250
|
+
let connectedPeers = await this.ipfs.libp2p.getPeers()
|
|
251
|
+
connectedPeers = connectedPeers.map(x => x.toString())
|
|
252
|
+
console.log('connectedPeers: ', connectedPeers)
|
|
253
|
+
|
|
254
|
+
return connectedPeers
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error('Error in ipfs-adapter.js/getPeers(): ', err)
|
|
257
|
+
throw err
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// module.exports = IpfsAdapter
|
|
263
|
+
export default IpfsAdapter
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Controls the verbosity of the status log
|
|
3
|
+
|
|
4
|
+
Default verbosity is zero, which is the least verbose messages.
|
|
5
|
+
As the value increases, the amount of messages also increases.
|
|
6
|
+
|
|
7
|
+
0 - Normal logs
|
|
8
|
+
1 - More information on connections and error connections.
|
|
9
|
+
2 - Verbose Error messages
|
|
10
|
+
3 - Error messages about connections.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// const util = require('util')
|
|
14
|
+
import util from 'util'
|
|
15
|
+
|
|
16
|
+
class LogsAdapter {
|
|
17
|
+
constructor (localConfig = {}) {
|
|
18
|
+
// Default to debugLevel 0 if not specified.
|
|
19
|
+
this.debugLevel = localConfig.debugLevel
|
|
20
|
+
if (!this.debugLevel) this.debugLevel = 0
|
|
21
|
+
|
|
22
|
+
this.logHandler = localConfig.statusLog
|
|
23
|
+
if (!this.logHandler) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'statusLog must be specified when instantiating Logs adapter library.'
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Print out the data, if the log level is less than or equal to the debug
|
|
31
|
+
// level set when instantiating the library.
|
|
32
|
+
statusLog (level, str, object) {
|
|
33
|
+
if (level <= this.debugLevel) {
|
|
34
|
+
if (object === undefined) {
|
|
35
|
+
this.logHandler('status: ' + str)
|
|
36
|
+
} else {
|
|
37
|
+
this.logHandler('status: ' + str + ' ' + util.inspect(object))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// module.exports = LogsAdapter
|
|
44
|
+
export default LogsAdapter
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Pubsub Adapter
|
|
2
|
+
|
|
3
|
+
The [index.js](./index.js) file contains the primary PubSub adapter, which controls the pubsub channel connections between peers. [messaging.js](./messaging.js) controls the message handling for communicating over pubsub channels.
|
|
4
|
+
|
|
5
|
+
Because IPFS nodes are constantly changing their network connections, it's frequently observed that pubsub messages between peers get 'lost'. Version 6 and older used [orbit-db](https://www.npmjs.com/package/orbit-db) to prevent these lost messages. However, it turned out to not be a very scalable solution. Orbit-db is way too CPU heavy to work as a speedy form of inter-node communication.
|
|
6
|
+
|
|
7
|
+
Version 7 introduced the [messaging.js](./messaging.js) library. The primary problem this library solves is the 'lost message' issue. The IPFS pubsub channels handle the bulk of the low-level messaging.
|
|
8
|
+
|
|
9
|
+
## Messaging Protocol
|
|
10
|
+
|
|
11
|
+
To handle 'lost messages', two peers engage in a message-acknowledge scheme:
|
|
12
|
+
|
|
13
|
+
Happy Path:
|
|
14
|
+
|
|
15
|
+
- Node 1 publishes a message to the pubsub channel for Node 2.
|
|
16
|
+
- Node 2 publishes an ACK (acknowledge) message to the pubsub channel for Node 1.
|
|
17
|
+
- If both messages are received, the transaction is complete.
|
|
18
|
+
|
|
19
|
+
Each message is wrapped in a data object with the following properties:
|
|
20
|
+
|
|
21
|
+
- timestamp
|
|
22
|
+
- UUID
|
|
23
|
+
- sender (IPFS ID)
|
|
24
|
+
- receiver (IPFS ID)
|
|
25
|
+
- payload
|
|
26
|
+
|
|
27
|
+
If Node 1 does not receive an ACK message after 5 seconds, it will publish the message to the pubsub channel again. It will do this every 5 seconds, until either an ACK message is received or a retry threshold is met (3 tries).
|
|
28
|
+
|
|
29
|
+
Node 2 will attempt to send an ACK message any time it receives a message.
|
|
30
|
+
|
|
31
|
+
It's important to note that two pubsub channels are used. Node 1 sends data on Node 2's pubsub channel. Node 2 responds by publishing data on Node 1's pubsub channel.
|
|
32
|
+
|
|
33
|
+
## Libraries
|
|
34
|
+
|
|
35
|
+
To understand the relationships between the messaing (messaging.js) and pubsub (index.js) libraries, and comparison to the [OSI model](https://www.imperva.com/learn/application-security/osi-model/) can be made. In the OSI model, from top to bottom, there is:
|
|
36
|
+
|
|
37
|
+
- A presentation layer (6).
|
|
38
|
+
- A session layer (5)
|
|
39
|
+
- A transport layer (4)
|
|
40
|
+
|
|
41
|
+
Analogously:
|
|
42
|
+
- The pubsub library ([index.js](./index.js)) is like the presentation layer (6).
|
|
43
|
+
- The messaging library ([messaging.js](./messaging.js)) is like the session layer (5).
|
|
44
|
+
- The IPFS pubsub API is like the transport layer (4).
|
|
45
|
+
|
|
46
|
+
The point is that there are two different communication protocols happening at the same time:
|
|
47
|
+
- The underlying messaging protocol described here *does not* care about the content of the messages. It's just trying to ensure messages are passes reliably.
|
|
48
|
+
- The message handling in the pubsub library *does* care about the content of the messages.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Example
|
|
52
|
+
|
|
53
|
+
Here is an example of an RPC command wrapped inside of a message envelope. The payload contains the encrypted RPC command. Notice the message itself has a UUID (universally unique identifier).
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
{
|
|
57
|
+
"timestamp": "2022-03-03T18:03:01.217Z",
|
|
58
|
+
"uuid": "e0f08e3f-6e23-43ea-8fa9-39b828fd4fdc",
|
|
59
|
+
"sender": "12D3KooWHS5A6Ey4V8fLWD64jpPn2EKi4r4btGN6FfkNgMTnfqVa",
|
|
60
|
+
"receiver": "12D3KooWE6tkdArVpCHG9QN61G1cE7eCq2Q7i4bNx6CJFTDprk9f",
|
|
61
|
+
"payload": "0453769b22a27adbb288bb2a05c19605a1fb033d4aeca92dfbf59f5c61732f89e6668a9a645dea3fe82e1bda20b8b11e713586b2c39d33f00dcf8fddac401263c320f06136a494e965f34193c3c6bb670a146c2ec06cdb5fd564b11a25c8715d574b8e1fd57f90e697c1edf21eb27ec0c431ce83293a4611e9593a53490c220019867208a1e241f23851c91646521b2e47"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Once the payload is decrypted and parsed, it looks like this. Notice that the RPC command has it's own UUID.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
{
|
|
69
|
+
jsonrpc: '2.0',
|
|
70
|
+
id: 'bb788af4-b7d1-4650-a7ae-bfd03b7f5140',
|
|
71
|
+
method: 'bch',
|
|
72
|
+
params: {
|
|
73
|
+
endpoint: 'utxos',
|
|
74
|
+
address: 'bitcoincash:qr2u4f2dmva6yvf3npkd5lquryp09qk7gs5vxl423h'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Workflow
|
|
80
|
+
|
|
81
|
+
Using the above RPC command as an example, here is the messaging workflow between Node 1 and Node 2:
|
|
82
|
+
|
|
83
|
+
- Node 1 sends the RPC command to Node 2
|
|
84
|
+
- Node 2 immediately responds with an ACK message
|
|
85
|
+
- Node 2 processes the RPC command and sends the response to Node 1
|
|
86
|
+
- Node 1 immediately reponds with an ACK message
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This adapter is designed to poll the /about JSON RPC endpoint for IPFS Service
|
|
3
|
+
Providers that leverage ipfs-coord. This allows other consumers of the
|
|
4
|
+
ipfs-coord library to measure the latency between themselves and potential
|
|
5
|
+
Circuit Relays.
|
|
6
|
+
|
|
7
|
+
Using these metrics, IPFS nodes can prioritize the Circuit Relays with the
|
|
8
|
+
lowest latency. At scale, this allows the entire IPFS subnet to adapt to
|
|
9
|
+
changing network conditions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
class AboutAdapter {
|
|
13
|
+
constructor (localConfig = {}) {
|
|
14
|
+
// Time to wait for a reponse from the RPC.
|
|
15
|
+
this.waitPeriod = 10000
|
|
16
|
+
|
|
17
|
+
// Used to pass asynchronous data when pubsub data is received.
|
|
18
|
+
this.incomingData = false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Query the /about JSON RPC endpoint for a subnet peer.
|
|
22
|
+
// This function will return true on success or false on failure or timeout
|
|
23
|
+
// of 10 seconds.
|
|
24
|
+
// This function is used to measure the time for a response from the peer.
|
|
25
|
+
async queryAbout (ipfsId, thisNode) {
|
|
26
|
+
try {
|
|
27
|
+
// console.log(`Querying Relay ${ipfsId}`)
|
|
28
|
+
// console.log('thisNode: ', thisNode)
|
|
29
|
+
|
|
30
|
+
// Generate the JSON RPC command
|
|
31
|
+
const idNum = Math.floor(Math.random() * 10000).toString()
|
|
32
|
+
const id = `metrics${idNum}`
|
|
33
|
+
const cmdStr = `{"jsonrpc":"2.0","id":"${id}","method":"about"}`
|
|
34
|
+
// console.log(`cmdStr: ${cmdStr}`)
|
|
35
|
+
|
|
36
|
+
// console.log(`Sending JSON RPC /about command to ${ipfsId}`)
|
|
37
|
+
const result = await this.sendRPC(ipfsId, cmdStr, id, thisNode)
|
|
38
|
+
// console.log('sendRPC result: ', result)
|
|
39
|
+
|
|
40
|
+
return result
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error('Error in queryAbout()')
|
|
43
|
+
|
|
44
|
+
// Do not throw an error.
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// This function is called by pubsub.captureMetrics() when a response is
|
|
50
|
+
// recieved to an /about request. The data is used by sendRPC().
|
|
51
|
+
relayMetricsReceived (inData) {
|
|
52
|
+
this.incomingData = inData
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Send the RPC command to the service, wait a period of time for a response.
|
|
56
|
+
// Timeout if a response is not recieved.
|
|
57
|
+
async sendRPC (ipfsId, cmdStr, id, thisNode) {
|
|
58
|
+
try {
|
|
59
|
+
let retData = this.incomingData
|
|
60
|
+
|
|
61
|
+
// Send the RPC command to the server/service.
|
|
62
|
+
await thisNode.useCases.peer.sendPrivateMessage(ipfsId, cmdStr, thisNode)
|
|
63
|
+
|
|
64
|
+
// Used for calculating the timeout.
|
|
65
|
+
const start = new Date()
|
|
66
|
+
let now = start
|
|
67
|
+
let timeDiff = 0
|
|
68
|
+
|
|
69
|
+
// Wait for the response from the server. Exit once the response is
|
|
70
|
+
// recieved, or a timeout occurs.
|
|
71
|
+
do {
|
|
72
|
+
await thisNode.useCases.peer.adapters.bch.bchjs.Util.sleep(250)
|
|
73
|
+
|
|
74
|
+
now = new Date()
|
|
75
|
+
|
|
76
|
+
timeDiff = now.getTime() - start.getTime()
|
|
77
|
+
// console.log('timeDiff: ', timeDiff)
|
|
78
|
+
|
|
79
|
+
retData = this.incomingData
|
|
80
|
+
|
|
81
|
+
// If data came in on the event emitter, analize it.
|
|
82
|
+
if (retData) {
|
|
83
|
+
// console.log('retData: ', retData)
|
|
84
|
+
|
|
85
|
+
const jsonData = JSON.parse(retData)
|
|
86
|
+
const respId = jsonData.id
|
|
87
|
+
|
|
88
|
+
// If the JSON RPC ID matches, then it's the response thisNode was
|
|
89
|
+
// waiting for.
|
|
90
|
+
if (respId === id) {
|
|
91
|
+
// responseRecieved = true
|
|
92
|
+
// this.eventEmitter.removeListener('relayMetrics', cb)
|
|
93
|
+
|
|
94
|
+
retData = false
|
|
95
|
+
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// console.log('retData: ', retData)
|
|
101
|
+
} while (
|
|
102
|
+
// Exit once the RPC data comes back, or if a period of time passes.
|
|
103
|
+
timeDiff < this.waitPeriod
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// this.eventEmitter.removeListener('relayMetrics', cb)
|
|
107
|
+
return false
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error('Error in sendRPC')
|
|
110
|
+
// this.eventEmitter.removeListener('relayMetrics', cb)
|
|
111
|
+
throw err
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// module.exports = AboutAdapter
|
|
117
|
+
export default AboutAdapter
|