helia-coord 1.1.3 → 1.2.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/config/global-config.js +13 -0
- package/dev-docs/theory-of-operation.md +33 -0
- package/lib/adapters/bch-adapter.js +0 -18
- package/lib/adapters/encryption-adapter.js +1 -45
- package/lib/adapters/index.js +0 -4
- package/lib/adapters/ipfs-adapter.js +26 -122
- package/lib/adapters/pubsub-adapter/index.js +54 -65
- package/lib/adapters/pubsub-adapter/messaging.js +10 -43
- package/lib/adapters/pubsub-adapter/msg-router.js +63 -2
- package/lib/controllers/timer-controller.js +7 -45
- package/lib/use-cases/peer-use-cases.js +0 -27
- package/lib/use-cases/pubsub-use-cases.js +9 -4
- package/lib/use-cases/relay-use-cases.js +39 -85
- package/lib/use-cases/this-node-use-cases.js +19 -49
- package/package.json +5 -3
- package/test/unit/adapters/index-adapters-unit.js +0 -6
- package/test/unit/adapters/ipfs-adapter-unit.js +8 -8
- package/test/unit/adapters/pubsub/pubsub-adapter-unit.js +171 -7
- package/test/unit/controllers/timer-controller-unit.js +0 -8
- package/test/unit/use-cases/peer.unit.use-cases.js +0 -14
- package/test/unit/use-cases/relay-use-cases-unit.js +56 -31
- package/test/unit/use-cases/this-node-use-cases-unit.js +0 -13
- package/test/unit/use-cases/use-cases-index-unit.js +0 -13
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Config file for storing global configuration settings.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const config = {
|
|
6
|
+
DEFAULT_COORDINATION_ROOM: 'psf-ipfs-coordination-002',
|
|
7
|
+
BCH_COINJOIN_ROOM: 'bch-coinjoin-001',
|
|
8
|
+
|
|
9
|
+
// Time between retrying private messages to a peer.
|
|
10
|
+
TIME_BETWEEN_RETRIES: 5000
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default config
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Theory of Operation
|
|
2
|
+
|
|
3
|
+
This document provides a high-level overview of what the helia-coord library does.
|
|
4
|
+
Helia-coord is middleware that controls a Helia IPFS node. It is used to form an on-the-fly, self-healing mesh network of other IPFS nodes. The nodes communicate over *pubsub channels*, and state is managed by *interval timers*.
|
|
5
|
+
|
|
6
|
+
## Pubsub Channels
|
|
7
|
+
|
|
8
|
+
A series of pubsub channels are opened between the IPFS nodes on the network.
|
|
9
|
+
|
|
10
|
+
### Coordination Channel
|
|
11
|
+
The coordination channel is a public, unencrypted channel that each node subscribes to. Every two minutes, a node will broadcast an 'announcement object', which contains information about the node and how to connect to it. This includes:
|
|
12
|
+
|
|
13
|
+
- The nodes IPFS ID
|
|
14
|
+
- Multiaddrs that can be used for other nodes to connect to it directly.
|
|
15
|
+
- The nodes public encryption key so that other nodes can send it e2ee messages.
|
|
16
|
+
- The nodes BCH and SLP addresses so that other nodes can send it payments and tokens.
|
|
17
|
+
- Metadata that describes what services the node offers and what versions it runs.
|
|
18
|
+
|
|
19
|
+
### CoinJoin Channel
|
|
20
|
+
This channel is not fully developed. It's another public, unencrypted channel that will be used for nodes to coordinate a CoinJoin transaction for achieving financial privacy. This channel can be ignored for now.
|
|
21
|
+
|
|
22
|
+
### This Nodes Private Channel
|
|
23
|
+
A pubsub channel is created using the nodes IPFS ID. Other nodes on the network will subscribe to this channel in order to send it encrypted messages. This channel is only used for receiving messages. The node never broadcasts messages on this channel.
|
|
24
|
+
|
|
25
|
+
The helia-coord library is consumed by higher-level software like [the pay-to-write database (P2WDB)](https://p2wdb.com), and different software comprising [the Cash Stack](https://cashstack.info). Once RPC commands received on this channel are decrypted, they are passed up to the consuming software via the `privateLog` object.
|
|
26
|
+
|
|
27
|
+
There are some low-level messages that are not passed up to the consuming software. These are ACK (acknowledge) and metric commands. The metrics commands are primarily used to measure and track latency between nodes and Circuit Relays. The helia-coord library will adjust its connection to achieve the lowest latency connections to other nodes on the network.
|
|
28
|
+
|
|
29
|
+
### Other Nodes Private Channel
|
|
30
|
+
When a new node is discovered via its announcement on the *Coordination Channel*, the node will subscribe to that nodes private channel. The node will use this channel to send encrypted RPC commands to other nodes. This channel is only used for broadcasting. It is never used for receiving messages.
|
|
31
|
+
|
|
32
|
+
## Interval Timers
|
|
33
|
+
A series of interval timers are defined in the `lib/controllser/timer-controller.js` file. These timers are periodically triggered in order to maintain the nodes state. They renew broken connections to other nodes, track latency between the node and Circuit Relays that it knows about, and other operations. These time-based function calls are what allow the node to create the mesh network on-the-fly and the self-heal the network as nodes come online or drop off the network.
|
|
@@ -23,15 +23,6 @@ class BchAdapter {
|
|
|
23
23
|
// derivation path of 245 if a 12 word mnemonic is provided.
|
|
24
24
|
async generateBchId () {
|
|
25
25
|
try {
|
|
26
|
-
// Generate a 12-word mnemonic, if one isn't provided.
|
|
27
|
-
// if (!this.mnemonic) {
|
|
28
|
-
// this.mnemonic = this.bchjs.Mnemonic.generate(
|
|
29
|
-
// 128,
|
|
30
|
-
// this.bchjs.Mnemonic.wordLists().english
|
|
31
|
-
// )
|
|
32
|
-
// }
|
|
33
|
-
// console.log(`mnemonic: ${mnemonic}`)
|
|
34
|
-
|
|
35
26
|
// root seed buffer
|
|
36
27
|
const rootSeed = await this.bchjs.Mnemonic.toSeed(this.mnemonic)
|
|
37
28
|
|
|
@@ -64,15 +55,6 @@ class BchAdapter {
|
|
|
64
55
|
|
|
65
56
|
async generatePrivateKey () {
|
|
66
57
|
try {
|
|
67
|
-
// Generate a 12-word mnemonic, if one isn't provided.
|
|
68
|
-
// if (!this.mnemonic) {
|
|
69
|
-
// this.mnemonic = this.bchjs.Mnemonic.generate(
|
|
70
|
-
// 128,
|
|
71
|
-
// this.bchjs.Mnemonic.wordLists().english
|
|
72
|
-
// )
|
|
73
|
-
// }
|
|
74
|
-
// console.log(`mnemonic: ${mnemonic}`)
|
|
75
|
-
|
|
76
58
|
// root seed buffer
|
|
77
59
|
const rootSeed = await this.bchjs.Mnemonic.toSeed(this.mnemonic)
|
|
78
60
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
for existing encryption libraries.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import BchEncrypt from 'bch-encrypt-lib
|
|
6
|
+
import BchEncrypt from 'bch-encrypt-lib'
|
|
7
7
|
|
|
8
8
|
class EncryptionAdapter {
|
|
9
9
|
constructor (localConfig = {}) {
|
|
@@ -24,8 +24,6 @@ class EncryptionAdapter {
|
|
|
24
24
|
// Encapsulate dependencies
|
|
25
25
|
this.bchjs = this.bch.bchjs // Copy of bch-js
|
|
26
26
|
this.bchEncrypt = new BchEncrypt({ bchjs: this.bchjs })
|
|
27
|
-
// this.ipfs = encryptConfig.ipfs
|
|
28
|
-
// this.orbitdb = encryptConfig.orbitdb
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
// Decrypt incoming messages on the pubsub channel for this node.
|
|
@@ -52,10 +50,6 @@ class EncryptionAdapter {
|
|
|
52
50
|
// Exit quietly if the issue is a 'Bad MAC'. This seems to be a startup
|
|
53
51
|
// issue.
|
|
54
52
|
if (err.message.includes('Bad MAC')) {
|
|
55
|
-
// throw new Error(
|
|
56
|
-
// 'Bad MAC. Could not decrypt message. Peer may have stale encryption data for this node.'
|
|
57
|
-
// )
|
|
58
|
-
|
|
59
53
|
this.log.statusLog(
|
|
60
54
|
2,
|
|
61
55
|
`Bad MAC. Could not decrypt message. Peer ${sender} may have stale encryption data for this node.`
|
|
@@ -92,44 +86,6 @@ class EncryptionAdapter {
|
|
|
92
86
|
throw err
|
|
93
87
|
}
|
|
94
88
|
}
|
|
95
|
-
|
|
96
|
-
// Send an e2e encrypted message to a peer.
|
|
97
|
-
// async sendEncryptedMsg (peer, msg) {
|
|
98
|
-
// try {
|
|
99
|
-
// // console.log('sendEncryptedMsg peer: ', peer)
|
|
100
|
-
// // console.log('sendEncryptedMsg msg: ', msg)
|
|
101
|
-
//
|
|
102
|
-
// // const channel = peer.ipfsId.toString()
|
|
103
|
-
// const pubKey = peer.encryptPubKey
|
|
104
|
-
// // const orbitdbId = peer.orbitdb
|
|
105
|
-
//
|
|
106
|
-
// const msgBuf = Buffer.from(msg, 'utf8').toString('hex')
|
|
107
|
-
// // console.log(`msgBuf: ${msgBuf}`)
|
|
108
|
-
//
|
|
109
|
-
// const encryptedHexStr = await this.bchEncrypt.encryption.encryptFile(
|
|
110
|
-
// pubKey,
|
|
111
|
-
// msgBuf
|
|
112
|
-
// )
|
|
113
|
-
// console.log(`encryptedHexStr: ${encryptedHexStr}`)
|
|
114
|
-
//
|
|
115
|
-
// // const msgBuf2 = Buffer.from(encryptedHexStr, 'hex')
|
|
116
|
-
//
|
|
117
|
-
// // Publish the message to the pubsub channel.
|
|
118
|
-
// // TODO: This will be deprecated in the future in favor of publishing to
|
|
119
|
-
// // the peers OrbitDB.
|
|
120
|
-
// // await this.ipfs.pubsub.publish(channel, msgBuf2)
|
|
121
|
-
//
|
|
122
|
-
// // if (orbitdbId) {
|
|
123
|
-
// // console.log(
|
|
124
|
-
// // `Ready to send encrypted message to peer ${channel} on orbitdb ID ${orbitdbId}`
|
|
125
|
-
// // )
|
|
126
|
-
// // await this.orbitdb.sendToDb(channel, encryptedHexStr, orbitdbId)
|
|
127
|
-
// // }
|
|
128
|
-
// } catch (err) {
|
|
129
|
-
// console.error('Error in sendEncryptedMsg()')
|
|
130
|
-
// throw err
|
|
131
|
-
// }
|
|
132
|
-
// }
|
|
133
89
|
}
|
|
134
90
|
|
|
135
91
|
// module.exports = EncryptionAdapter
|
package/lib/adapters/index.js
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Public npm libraries
|
|
7
|
-
// const EventEmitter = require('events')
|
|
8
|
-
// EventEmitter.defaultMaxListeners = 200
|
|
9
7
|
|
|
10
8
|
// Local libraries
|
|
11
9
|
import LogsAdapter from './logs-adapter.js'
|
|
@@ -15,8 +13,6 @@ import EncryptionAdapter from './encryption-adapter.js'
|
|
|
15
13
|
import PubsubAdapter from './pubsub-adapter/index.js'
|
|
16
14
|
import Gist from './gist.js'
|
|
17
15
|
|
|
18
|
-
// const Gist = require('./gist')
|
|
19
|
-
|
|
20
16
|
class Adapters {
|
|
21
17
|
constructor (localConfig = {}) {
|
|
22
18
|
// Dependency injection
|
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
know specifics about the IPFS API.
|
|
4
4
|
*/
|
|
5
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
6
|
import { multiaddr } from '@multiformats/multiaddr'
|
|
11
7
|
|
|
12
8
|
const CONNECTION_TIMEOUT = 10000
|
|
@@ -27,6 +23,12 @@ class IpfsAdapter {
|
|
|
27
23
|
)
|
|
28
24
|
}
|
|
29
25
|
|
|
26
|
+
// Bind 'this' object to all subfunctions
|
|
27
|
+
this.start = this.start.bind(this)
|
|
28
|
+
this.connectToPeer = this.connectToPeer.bind(this)
|
|
29
|
+
this.disconnectFromPeer = this.disconnectFromPeer.bind(this)
|
|
30
|
+
this.getPeers = this.getPeers.bind(this)
|
|
31
|
+
|
|
30
32
|
// 'embedded' node type used as default, will use embedded js-ipfs.
|
|
31
33
|
// Alternative is 'external' which will use ipfs-http-client to control an
|
|
32
34
|
// external IPFS node.
|
|
@@ -59,93 +61,12 @@ class IpfsAdapter {
|
|
|
59
61
|
this.ipfs = await this.ipfs
|
|
60
62
|
|
|
61
63
|
// 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
64
|
this.ipfsPeerId = this.ipfs.libp2p.peerId.toString()
|
|
66
65
|
|
|
67
66
|
// Get multiaddrs that can be used to connect to this node.
|
|
68
|
-
// const addrs = id2.addresses.map(elem => elem.toString())
|
|
69
67
|
let addrs = this.ipfs.libp2p.getMultiaddrs()
|
|
70
68
|
addrs = addrs.map(elem => elem.toString())
|
|
71
|
-
// this.state.ipfsMultiaddrs = addrs
|
|
72
69
|
this.ipfsMultiaddrs = addrs
|
|
73
|
-
|
|
74
|
-
// Remove bootstrap nodes, as we have our own bootstrap nodes, and the
|
|
75
|
-
// the default ones can spam our nodes with a ton of bandwidth.
|
|
76
|
-
// await this.ipfs.config.set('Bootstrap', [
|
|
77
|
-
// bootstapNodes.node[0].multiaddr,
|
|
78
|
-
// bootstapNodes.node[1].multiaddr
|
|
79
|
-
// ])
|
|
80
|
-
|
|
81
|
-
// Settings specific to embedded js-ipfs.
|
|
82
|
-
// if (this.nodeType === 'embedded') {
|
|
83
|
-
// // Also remove default Delegates, as they are the same as the default
|
|
84
|
-
// // Bootstrap nodes.
|
|
85
|
-
// await this.ipfs.config.set('Addresses.Delegates', [])
|
|
86
|
-
// }
|
|
87
|
-
|
|
88
|
-
// Settings specific to external go-ipfs node.
|
|
89
|
-
// if (this.nodeType === 'external') {
|
|
90
|
-
// // Enable RelayClient
|
|
91
|
-
// // https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#swarmrelayclient
|
|
92
|
-
// // https://github.com/ipfs/go-ipfs/releases/tag/v0.11.0
|
|
93
|
-
// await this.ipfs.config.set('Swarm.RelayClient.Enabled', true)
|
|
94
|
-
|
|
95
|
-
// // Enable hole punching for better p2p interaction.
|
|
96
|
-
// // https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#swarmenableholepunching
|
|
97
|
-
// await this.ipfs.config.set('Swarm.EnableHolePunching', true)
|
|
98
|
-
|
|
99
|
-
// // Enable websocket connections
|
|
100
|
-
// await this.ipfs.config.set('Addresses.Swarm', [
|
|
101
|
-
// `/ip4/0.0.0.0/tcp/${this.tcpPort}`,
|
|
102
|
-
// `/ip6/::/tcp/${this.tcpPort}`,
|
|
103
|
-
// // `/ip4/0.0.0.0/udp/${this.tcpPort}/quic`,
|
|
104
|
-
// // `/ip6/::/udp/${this.tcpPort}/quic`,
|
|
105
|
-
// `/ip4/0.0.0.0/tcp/${this.wsPort}`, // Websockets
|
|
106
|
-
// `/ip6/::/tcp/${this.wsPort}`
|
|
107
|
-
// ])
|
|
108
|
-
|
|
109
|
-
// // Disable scanning of IP ranges. This is largely driven by Hetzner
|
|
110
|
-
// await this.ipfs.config.set('Swarm.AddrFilters', [
|
|
111
|
-
// '/ip4/10.0.0.0/ipcidr/8',
|
|
112
|
-
// '/ip4/100.0.0.0/ipcidr/8',
|
|
113
|
-
// '/ip4/169.254.0.0/ipcidr/16',
|
|
114
|
-
// '/ip4/172.16.0.0/ipcidr/12',
|
|
115
|
-
// '/ip4/192.0.0.0/ipcidr/24',
|
|
116
|
-
// '/ip4/192.0.2.0/ipcidr/24',
|
|
117
|
-
// '/ip4/192.168.0.0/ipcidr/16',
|
|
118
|
-
// '/ip4/198.18.0.0/ipcidr/15',
|
|
119
|
-
// '/ip4/198.51.100.0/ipcidr/24',
|
|
120
|
-
// '/ip4/203.0.113.0/ipcidr/24',
|
|
121
|
-
// '/ip4/240.0.0.0/ipcidr/4',
|
|
122
|
-
// '/ip6/100::/ipcidr/64',
|
|
123
|
-
// '/ip6/2001:2::/ipcidr/48',
|
|
124
|
-
// '/ip6/2001:db8::/ipcidr/32',
|
|
125
|
-
// '/ip6/fc00::/ipcidr/7',
|
|
126
|
-
// '/ip6/fe80::/ipcidr/10'
|
|
127
|
-
// ])
|
|
128
|
-
|
|
129
|
-
// // go-ipfs v0.10.0
|
|
130
|
-
// // await this.ipfs.config.set('Swarm.EnableRelayHop', true)
|
|
131
|
-
// // await this.ipfs.config.set('Swarm.EnableAutoRelay', true)
|
|
132
|
-
|
|
133
|
-
// // Disable peer discovery
|
|
134
|
-
// // await this.ipfs.config.set('Routing.Type', 'none')
|
|
135
|
-
// }
|
|
136
|
-
|
|
137
|
-
// Disable preloading
|
|
138
|
-
// await this.ipfs.config.set('preload.enabled', false)
|
|
139
|
-
|
|
140
|
-
// Reduce the default number of peers thisNode connects to at one time.
|
|
141
|
-
// await this.ipfs.config.set('Swarm.ConnMgr', {
|
|
142
|
-
// LowWater: 10,
|
|
143
|
-
// HighWater: 30,
|
|
144
|
-
// GracePeriod: '2s'
|
|
145
|
-
// })
|
|
146
|
-
|
|
147
|
-
// Reduce the storage size, as this node should not be retaining much data.
|
|
148
|
-
// await this.ipfs.config.set('Datastore.StorageMax', '2GB')
|
|
149
70
|
} catch (err) {
|
|
150
71
|
console.error('Error in ipfs-adapter.js/start()')
|
|
151
72
|
throw err
|
|
@@ -154,43 +75,34 @@ class IpfsAdapter {
|
|
|
154
75
|
|
|
155
76
|
// Attempts to connect to an IPFS peer, given its IPFS multiaddr.
|
|
156
77
|
// Returns true if the connection succeeded. Otherwise returns false.
|
|
157
|
-
async connectToPeer (
|
|
78
|
+
async connectToPeer (inObj = {}) {
|
|
158
79
|
try {
|
|
80
|
+
// console.log('connectToPeer() inObj: ', inObj)
|
|
81
|
+
|
|
159
82
|
// TODO: Throw error if ipfs ID is passed, instead of a multiaddr.
|
|
160
|
-
console.log('ipfsAddr: ', ipfsAddr)
|
|
83
|
+
// console.log('ipfsAddr: ', ipfsAddr)
|
|
84
|
+
|
|
85
|
+
const { multiaddr } = inObj
|
|
161
86
|
|
|
162
87
|
// await this.ipfs.swarm.connect(ipfsAddr, { timeout: CONNECTION_TIMEOUT })
|
|
163
|
-
await this.ipfs.libp2p.dial(this.multiaddr(
|
|
88
|
+
await this.ipfs.libp2p.dial(this.multiaddr(multiaddr))
|
|
89
|
+
// console.log('connectToPeer() result: ', result)
|
|
164
90
|
|
|
165
|
-
this.log.statusLog(1, `Successfully connected to peer node ${
|
|
91
|
+
this.log.statusLog(1, `Successfully connected to peer node ${multiaddr}`)
|
|
166
92
|
|
|
167
|
-
return
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
details: null
|
|
96
|
+
}
|
|
168
97
|
} catch (err) {
|
|
169
98
|
/* exit quietly */
|
|
170
|
-
console.
|
|
171
|
-
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
// if (this.debugLevel === 1) {
|
|
175
|
-
// this.statusLog(
|
|
176
|
-
// `status: Error trying to connect to peer node ${ipfsAddr}`
|
|
177
|
-
// )
|
|
178
|
-
// } else if (this.debugLevel === 2) {
|
|
179
|
-
// this.statusLog(
|
|
180
|
-
// `status: Error trying to connect to peer node ${ipfsAddr}: `,
|
|
181
|
-
// err
|
|
182
|
-
// )
|
|
183
|
-
// }
|
|
184
|
-
this.log.statusLog(2, `Error trying to connect to peer node ${ipfsAddr}`)
|
|
185
|
-
// console.log(`Error trying to connect to peer node ${ipfsAddr}: `, err)
|
|
186
|
-
|
|
187
|
-
// this.log.statusLog(
|
|
188
|
-
// 3,
|
|
189
|
-
// `Error trying to connect to peer node ${ipfsAddr}: `,
|
|
190
|
-
// err
|
|
191
|
-
// )
|
|
99
|
+
// console.log('connectToPeer() Error connecting to peer: ', err)
|
|
100
|
+
this.log.statusLog(2, 'Error trying to connect to peer node : ', err.message)
|
|
192
101
|
|
|
193
|
-
return
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
details: err.message
|
|
105
|
+
}
|
|
194
106
|
}
|
|
195
107
|
}
|
|
196
108
|
|
|
@@ -241,16 +153,9 @@ class IpfsAdapter {
|
|
|
241
153
|
try {
|
|
242
154
|
// console.log('this.ipfs.libp2p: ', this.ipfs.libp2p)
|
|
243
155
|
|
|
244
|
-
// Get connected peers
|
|
245
|
-
// const connectedPeers = await this.ipfs.swarm.peers({
|
|
246
|
-
// direction: true,
|
|
247
|
-
// streams: true,
|
|
248
|
-
// verbose: true,
|
|
249
|
-
// latency: true
|
|
250
|
-
// })
|
|
251
156
|
let connectedPeers = await this.ipfs.libp2p.getPeers()
|
|
252
157
|
connectedPeers = connectedPeers.map(x => x.toString())
|
|
253
|
-
|
|
158
|
+
this.log.statusLog(1, 'connectedPeers: ', connectedPeers)
|
|
254
159
|
|
|
255
160
|
return connectedPeers
|
|
256
161
|
} catch (err) {
|
|
@@ -260,5 +165,4 @@ class IpfsAdapter {
|
|
|
260
165
|
}
|
|
261
166
|
}
|
|
262
167
|
|
|
263
|
-
// module.exports = IpfsAdapter
|
|
264
168
|
export default IpfsAdapter
|
|
@@ -9,7 +9,6 @@ import { BroadcastRouter, PrivateChannelRouter } from './msg-router.js'
|
|
|
9
9
|
|
|
10
10
|
// Libraries for working with default uint8Array Buffers that pubsub uses
|
|
11
11
|
// for sending and receiving messages.
|
|
12
|
-
// import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
13
12
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
14
13
|
|
|
15
14
|
class PubsubAdapter {
|
|
@@ -45,19 +44,20 @@ class PubsubAdapter {
|
|
|
45
44
|
this.messaging = new Messaging(localConfig)
|
|
46
45
|
this.about = new AboutAdapter(localConfig)
|
|
47
46
|
|
|
48
|
-
// 'embedded' node type used as default, will use embedded js-ipfs.
|
|
49
|
-
// Alternative is 'external' which will use ipfs-http-client to control an
|
|
50
|
-
// external IPFS node.
|
|
51
47
|
this.nodeType = localConfig.nodeType
|
|
52
48
|
if (!this.nodeType) {
|
|
53
|
-
|
|
54
|
-
this.nodeType = 'embedded'
|
|
49
|
+
this.nodeType = 'node.js'
|
|
55
50
|
}
|
|
56
|
-
// console.log(`PubsubAdapter contructor node type: ${this.nodeType}`)
|
|
57
51
|
|
|
58
52
|
// Bind functions that are called by event handlers
|
|
59
53
|
this.parsePubsubMessage = this.parsePubsubMessage.bind(this)
|
|
60
54
|
this.handleNewMessage = this.handleNewMessage.bind(this)
|
|
55
|
+
this.checkForDuplicateMsg = this.checkForDuplicateMsg.bind(this)
|
|
56
|
+
this.manageMsgCache = this.manageMsgCache.bind(this)
|
|
57
|
+
|
|
58
|
+
// State
|
|
59
|
+
this.trackedMsgs = [] // Used reject repeated messages
|
|
60
|
+
this.TRACKED_MSG_SIZE = 100
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// Subscribe to a pubsub channel. Any data received on that channel is passed
|
|
@@ -71,11 +71,6 @@ class PubsubAdapter {
|
|
|
71
71
|
// for this node. This applies to general broadcast channels like
|
|
72
72
|
// the coordination channel that all nodes use to annouce themselves.
|
|
73
73
|
if (chanName !== thisNodeId) {
|
|
74
|
-
// console.log('this.ipfs: ', this.ipfs)
|
|
75
|
-
// await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), async (msg) => {
|
|
76
|
-
// await this.parsePubsubMessage(msg, handler, thisNode)
|
|
77
|
-
// })
|
|
78
|
-
|
|
79
74
|
// Instantiate the Broadcast message router library
|
|
80
75
|
const bRouterOptions = {
|
|
81
76
|
handler,
|
|
@@ -105,19 +100,10 @@ class PubsubAdapter {
|
|
|
105
100
|
|
|
106
101
|
// Subscribe to the pubsbu channel. Route any incoming messages to the
|
|
107
102
|
// this library.
|
|
108
|
-
// await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), privateRouter.route)
|
|
109
103
|
this.ipfs.ipfs.libp2p.services.pubsub.subscribe(chanName.toString())
|
|
110
104
|
|
|
111
105
|
// Route incoming message events.
|
|
112
106
|
this.ipfs.ipfs.libp2p.services.pubsub.addEventListener('message', privateRouter.route)
|
|
113
|
-
|
|
114
|
-
// await this.ipfs.ipfs.pubsub.subscribe(chanName.toString(), async (msg) => {
|
|
115
|
-
// const msgObj = await this.messaging.handleIncomingData(msg, thisNode)
|
|
116
|
-
//
|
|
117
|
-
// // If msgObj is false, then ignore it. Typically indicates an already
|
|
118
|
-
// // processed message.
|
|
119
|
-
// if (msgObj) { await this.handleNewMessage(msgObj, thisNode) }
|
|
120
|
-
// })
|
|
121
107
|
}
|
|
122
108
|
|
|
123
109
|
this.log.statusLog(
|
|
@@ -150,7 +136,6 @@ class PubsubAdapter {
|
|
|
150
136
|
// Pass the JSON RPC data to the private log to be handled by the app
|
|
151
137
|
// consuming this library.
|
|
152
138
|
if (!isAbout) {
|
|
153
|
-
// console.log('handleNewMessage() forwarding payload on to handler.')
|
|
154
139
|
this.privateLog(msgObj.data.payload, msgObj.from)
|
|
155
140
|
|
|
156
141
|
return true
|
|
@@ -172,16 +157,11 @@ class PubsubAdapter {
|
|
|
172
157
|
// console.log('thisNode: ', thisNode)
|
|
173
158
|
|
|
174
159
|
const data = JSON.parse(decryptedStr)
|
|
175
|
-
// console.log('data: ', data)
|
|
176
160
|
|
|
177
161
|
// Handle /about JSON RPC queries.
|
|
178
162
|
if (data.id.includes('metrics') && data.method === 'about') {
|
|
179
163
|
// Request recieved, send response.
|
|
180
164
|
|
|
181
|
-
// console.log('/about JSON RPC captured. Sending back announce object.')
|
|
182
|
-
|
|
183
|
-
// console.log('thisNode.schema.state.announceJsonLd: ', thisNode.schema.state.announceJsonLd)
|
|
184
|
-
|
|
185
165
|
const jsonResponse = `{"jsonrpc": "2.0", "id": "${data.id}", "result": {"method": "about", "receiver": "${from}", "value": ${JSON.stringify(thisNode.schema.state)}}}`
|
|
186
166
|
// console.log(`Responding with this JSON RPC response: ${jsonResponse}`)
|
|
187
167
|
|
|
@@ -200,8 +180,6 @@ class PubsubAdapter {
|
|
|
200
180
|
} else if (data.id.includes('metrics') && data.result && data.result.method === 'about') {
|
|
201
181
|
// Response received.
|
|
202
182
|
|
|
203
|
-
// console.log('JSON RPC /about response aquired.')
|
|
204
|
-
|
|
205
183
|
// This event is handled by the about-adapter.js. It measures the
|
|
206
184
|
// latency between peers.
|
|
207
185
|
this.about.relayMetricsReceived(decryptedStr)
|
|
@@ -221,19 +199,13 @@ class PubsubAdapter {
|
|
|
221
199
|
// Attempts to parse data coming in from a pubsub channel. It is assumed that
|
|
222
200
|
// the data is a string in JSON format. If it isn't, parsing will throw an
|
|
223
201
|
// error and the message will be ignored.
|
|
224
|
-
async parsePubsubMessage (
|
|
202
|
+
async parsePubsubMessage (inObj = {}) {
|
|
225
203
|
try {
|
|
226
|
-
|
|
227
|
-
// console.log('parsePubsubMessage msg: ', msg)
|
|
228
|
-
// console.log('thisNode: ', thisNode)
|
|
204
|
+
const { msg, handler, thisNode } = inObj
|
|
229
205
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// console.log('typeof parsedData: ', typeof parsedData)
|
|
234
|
-
// console.log('msg.detail.data: ', JSON.stringify(parsedData, null, 2))
|
|
235
|
-
// console.log('msg.detail.topic: ', msg.detail.topic)
|
|
236
|
-
// console.log('msg.detail.from: ', msg.detail.from.toString())
|
|
206
|
+
// Skip any repeated messages
|
|
207
|
+
const shouldProcess = this.checkForDuplicateMsg(msg)
|
|
208
|
+
if (!shouldProcess) return false
|
|
237
209
|
|
|
238
210
|
const thisNodeId = thisNode.ipfsId
|
|
239
211
|
|
|
@@ -250,16 +222,6 @@ class PubsubAdapter {
|
|
|
250
222
|
// Ignore this message if it originated from this IPFS node.
|
|
251
223
|
if (from === thisNodeId) return true
|
|
252
224
|
|
|
253
|
-
// The data on browsers comes through as a uint8array, and on node.js
|
|
254
|
-
// implementiong it comes through as a string. Browsers need to
|
|
255
|
-
// convert the message from a uint8array to a string.
|
|
256
|
-
// console.log('this.nodeType: ', this.nodeType)
|
|
257
|
-
// if (thisNode.type === 'browser' || this.nodeType === 'external') {
|
|
258
|
-
// // console.log('Node type is browser or external')
|
|
259
|
-
// msg.data = new TextDecoder('utf-8').decode(msg.data)
|
|
260
|
-
// }
|
|
261
|
-
// console.log('msg.data: ', JSON.parse(msg.data.toString()))
|
|
262
|
-
|
|
263
225
|
let data
|
|
264
226
|
try {
|
|
265
227
|
// Parse the data into a JSON object. It starts as a Buffer that needs
|
|
@@ -269,38 +231,65 @@ class PubsubAdapter {
|
|
|
269
231
|
|
|
270
232
|
data = uint8ArrayToString(msg.detail.data)
|
|
271
233
|
data = JSON.parse(JSON.parse(data))
|
|
272
|
-
// console.log('data: ', data)
|
|
234
|
+
// console.log('parsePubsubMessage() 1 data: ', data)
|
|
273
235
|
|
|
274
|
-
//
|
|
275
|
-
// data.data = JSON.parse(data.data)
|
|
276
|
-
// }
|
|
277
|
-
// console.log('parsePubsubMessage() data: ', data)
|
|
236
|
+
// console.log('pubsub-adapter/index.js parsePubsubMessage() collect test data: ', msg.detail.data.toString('hex'))
|
|
278
237
|
} catch (err) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
throw new Error(`Failed to parse JSON in message from ${from} in pubsub channel ${channel}.`)
|
|
286
|
-
}
|
|
238
|
+
this.log.statusLog(
|
|
239
|
+
1,
|
|
240
|
+
`Failed to parse JSON in message from ${from} in pubsub channel ${channel}.`
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return false
|
|
287
244
|
}
|
|
288
245
|
|
|
289
246
|
const retObj = { from, channel, data }
|
|
290
247
|
// console.log(`new pubsub message received: ${JSON.stringify(retObj, null, 2)}`)
|
|
291
248
|
|
|
292
249
|
// Hand retObj to the callback.
|
|
293
|
-
handler(retObj)
|
|
250
|
+
await handler(retObj)
|
|
294
251
|
|
|
295
252
|
return true
|
|
296
253
|
} catch (err) {
|
|
297
|
-
|
|
254
|
+
console.error('Error in parsePubsubMessage(): ', err.message)
|
|
298
255
|
this.log.statusLog(2, `Error in parsePubsubMessage(): ${err.message}`)
|
|
299
256
|
// Do not throw an error. This is a top-level function.
|
|
300
257
|
|
|
301
258
|
return false
|
|
302
259
|
}
|
|
303
260
|
}
|
|
261
|
+
|
|
262
|
+
// Checks to see if a message has already been processed. This protects against
|
|
263
|
+
// redundent processing.
|
|
264
|
+
checkForDuplicateMsg (msg) {
|
|
265
|
+
const sn = msg.detail.sequenceNumber
|
|
266
|
+
// console.log('sn: ', sn)
|
|
267
|
+
|
|
268
|
+
const snExists = this.trackedMsgs.find((x) => x === sn)
|
|
269
|
+
// console.log('snExists: ', snExists)
|
|
270
|
+
|
|
271
|
+
// Maintain the tracked message cache.
|
|
272
|
+
this.manageMsgCache()
|
|
273
|
+
|
|
274
|
+
if (snExists) {
|
|
275
|
+
// console.log(`msg ${sn} already processed. Rejecting.`)
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Add the sn to the array of tracked messages.
|
|
280
|
+
this.trackedMsgs.push(sn)
|
|
281
|
+
|
|
282
|
+
return true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Keep the message queue to a resonable size.
|
|
286
|
+
manageMsgCache () {
|
|
287
|
+
// console.log(`trackedMsgs: `, this.trackedMsgs)
|
|
288
|
+
|
|
289
|
+
if (this.trackedMsgs.length > this.TRACKED_MSG_SIZE) {
|
|
290
|
+
this.trackedMsgs.shift()
|
|
291
|
+
}
|
|
292
|
+
}
|
|
304
293
|
}
|
|
305
294
|
|
|
306
295
|
// module.exports = PubsubAdapter
|