@libp2p/gossipsub 14.1.1-6059227cb

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.
Files changed (160) hide show
  1. package/README.md +85 -0
  2. package/dist/index.min.js +19 -0
  3. package/dist/index.min.js.map +7 -0
  4. package/dist/src/config.d.ts +32 -0
  5. package/dist/src/config.d.ts.map +1 -0
  6. package/dist/src/config.js +2 -0
  7. package/dist/src/config.js.map +1 -0
  8. package/dist/src/constants.d.ts +213 -0
  9. package/dist/src/constants.d.ts.map +1 -0
  10. package/dist/src/constants.js +217 -0
  11. package/dist/src/constants.js.map +1 -0
  12. package/dist/src/errors.d.ts +9 -0
  13. package/dist/src/errors.d.ts.map +1 -0
  14. package/dist/src/errors.js +15 -0
  15. package/dist/src/errors.js.map +1 -0
  16. package/dist/src/gossipsub.d.ts +419 -0
  17. package/dist/src/gossipsub.d.ts.map +1 -0
  18. package/dist/src/gossipsub.js +2520 -0
  19. package/dist/src/gossipsub.js.map +1 -0
  20. package/dist/src/index.d.ts +344 -0
  21. package/dist/src/index.d.ts.map +1 -0
  22. package/dist/src/index.js +43 -0
  23. package/dist/src/index.js.map +1 -0
  24. package/dist/src/message/decodeRpc.d.ts +11 -0
  25. package/dist/src/message/decodeRpc.d.ts.map +1 -0
  26. package/dist/src/message/decodeRpc.js +10 -0
  27. package/dist/src/message/decodeRpc.js.map +1 -0
  28. package/dist/src/message/index.d.ts +2 -0
  29. package/dist/src/message/index.d.ts.map +1 -0
  30. package/dist/src/message/index.js +2 -0
  31. package/dist/src/message/index.js.map +1 -0
  32. package/dist/src/message/rpc.d.ts +99 -0
  33. package/dist/src/message/rpc.d.ts.map +1 -0
  34. package/dist/src/message/rpc.js +663 -0
  35. package/dist/src/message/rpc.js.map +1 -0
  36. package/dist/src/message-cache.d.ts +80 -0
  37. package/dist/src/message-cache.d.ts.map +1 -0
  38. package/dist/src/message-cache.js +144 -0
  39. package/dist/src/message-cache.js.map +1 -0
  40. package/dist/src/metrics.d.ts +467 -0
  41. package/dist/src/metrics.d.ts.map +1 -0
  42. package/dist/src/metrics.js +896 -0
  43. package/dist/src/metrics.js.map +1 -0
  44. package/dist/src/score/compute-score.d.ts +4 -0
  45. package/dist/src/score/compute-score.d.ts.map +1 -0
  46. package/dist/src/score/compute-score.js +75 -0
  47. package/dist/src/score/compute-score.js.map +1 -0
  48. package/dist/src/score/index.d.ts +4 -0
  49. package/dist/src/score/index.d.ts.map +1 -0
  50. package/dist/src/score/index.js +4 -0
  51. package/dist/src/score/index.js.map +1 -0
  52. package/dist/src/score/message-deliveries.d.ts +45 -0
  53. package/dist/src/score/message-deliveries.d.ts.map +1 -0
  54. package/dist/src/score/message-deliveries.js +75 -0
  55. package/dist/src/score/message-deliveries.js.map +1 -0
  56. package/dist/src/score/peer-score-params.d.ts +125 -0
  57. package/dist/src/score/peer-score-params.d.ts.map +1 -0
  58. package/dist/src/score/peer-score-params.js +159 -0
  59. package/dist/src/score/peer-score-params.js.map +1 -0
  60. package/dist/src/score/peer-score-thresholds.d.ts +31 -0
  61. package/dist/src/score/peer-score-thresholds.d.ts.map +1 -0
  62. package/dist/src/score/peer-score-thresholds.js +32 -0
  63. package/dist/src/score/peer-score-thresholds.js.map +1 -0
  64. package/dist/src/score/peer-score.d.ts +119 -0
  65. package/dist/src/score/peer-score.d.ts.map +1 -0
  66. package/dist/src/score/peer-score.js +459 -0
  67. package/dist/src/score/peer-score.js.map +1 -0
  68. package/dist/src/score/peer-stats.d.ts +32 -0
  69. package/dist/src/score/peer-stats.d.ts.map +1 -0
  70. package/dist/src/score/peer-stats.js +2 -0
  71. package/dist/src/score/peer-stats.js.map +1 -0
  72. package/dist/src/score/scoreMetrics.d.ts +23 -0
  73. package/dist/src/score/scoreMetrics.d.ts.map +1 -0
  74. package/dist/src/score/scoreMetrics.js +155 -0
  75. package/dist/src/score/scoreMetrics.js.map +1 -0
  76. package/dist/src/stream.d.ts +30 -0
  77. package/dist/src/stream.d.ts.map +1 -0
  78. package/dist/src/stream.js +55 -0
  79. package/dist/src/stream.js.map +1 -0
  80. package/dist/src/tracer.d.ts +53 -0
  81. package/dist/src/tracer.d.ts.map +1 -0
  82. package/dist/src/tracer.js +155 -0
  83. package/dist/src/tracer.js.map +1 -0
  84. package/dist/src/types.d.ts +148 -0
  85. package/dist/src/types.d.ts.map +1 -0
  86. package/dist/src/types.js +90 -0
  87. package/dist/src/types.js.map +1 -0
  88. package/dist/src/utils/buildRawMessage.d.ts +20 -0
  89. package/dist/src/utils/buildRawMessage.d.ts.map +1 -0
  90. package/dist/src/utils/buildRawMessage.js +151 -0
  91. package/dist/src/utils/buildRawMessage.js.map +1 -0
  92. package/dist/src/utils/create-gossip-rpc.d.ts +7 -0
  93. package/dist/src/utils/create-gossip-rpc.d.ts.map +1 -0
  94. package/dist/src/utils/create-gossip-rpc.js +31 -0
  95. package/dist/src/utils/create-gossip-rpc.js.map +1 -0
  96. package/dist/src/utils/index.d.ts +4 -0
  97. package/dist/src/utils/index.d.ts.map +1 -0
  98. package/dist/src/utils/index.js +4 -0
  99. package/dist/src/utils/index.js.map +1 -0
  100. package/dist/src/utils/messageIdToString.d.ts +5 -0
  101. package/dist/src/utils/messageIdToString.d.ts.map +1 -0
  102. package/dist/src/utils/messageIdToString.js +8 -0
  103. package/dist/src/utils/messageIdToString.js.map +1 -0
  104. package/dist/src/utils/msgIdFn.d.ts +10 -0
  105. package/dist/src/utils/msgIdFn.d.ts.map +1 -0
  106. package/dist/src/utils/msgIdFn.js +23 -0
  107. package/dist/src/utils/msgIdFn.js.map +1 -0
  108. package/dist/src/utils/multiaddr.d.ts +3 -0
  109. package/dist/src/utils/multiaddr.d.ts.map +1 -0
  110. package/dist/src/utils/multiaddr.js +15 -0
  111. package/dist/src/utils/multiaddr.js.map +1 -0
  112. package/dist/src/utils/publishConfig.d.ts +8 -0
  113. package/dist/src/utils/publishConfig.d.ts.map +1 -0
  114. package/dist/src/utils/publishConfig.js +25 -0
  115. package/dist/src/utils/publishConfig.js.map +1 -0
  116. package/dist/src/utils/set.d.ts +14 -0
  117. package/dist/src/utils/set.d.ts.map +1 -0
  118. package/dist/src/utils/set.js +41 -0
  119. package/dist/src/utils/set.js.map +1 -0
  120. package/dist/src/utils/shuffle.d.ts +7 -0
  121. package/dist/src/utils/shuffle.d.ts.map +1 -0
  122. package/dist/src/utils/shuffle.js +21 -0
  123. package/dist/src/utils/shuffle.js.map +1 -0
  124. package/dist/src/utils/time-cache.d.ts +22 -0
  125. package/dist/src/utils/time-cache.d.ts.map +1 -0
  126. package/dist/src/utils/time-cache.js +54 -0
  127. package/dist/src/utils/time-cache.js.map +1 -0
  128. package/package.json +142 -0
  129. package/src/config.ts +31 -0
  130. package/src/constants.ts +261 -0
  131. package/src/errors.ts +17 -0
  132. package/src/gossipsub.ts +3061 -0
  133. package/src/index.ts +404 -0
  134. package/src/message/decodeRpc.ts +19 -0
  135. package/src/message/index.ts +1 -0
  136. package/src/message/rpc.proto +58 -0
  137. package/src/message/rpc.ts +848 -0
  138. package/src/message-cache.ts +196 -0
  139. package/src/metrics.ts +1014 -0
  140. package/src/score/compute-score.ts +98 -0
  141. package/src/score/index.ts +3 -0
  142. package/src/score/message-deliveries.ts +95 -0
  143. package/src/score/peer-score-params.ts +316 -0
  144. package/src/score/peer-score-thresholds.ts +70 -0
  145. package/src/score/peer-score.ts +565 -0
  146. package/src/score/peer-stats.ts +33 -0
  147. package/src/score/scoreMetrics.ts +215 -0
  148. package/src/stream.ts +79 -0
  149. package/src/tracer.ts +177 -0
  150. package/src/types.ts +178 -0
  151. package/src/utils/buildRawMessage.ts +174 -0
  152. package/src/utils/create-gossip-rpc.ts +34 -0
  153. package/src/utils/index.ts +3 -0
  154. package/src/utils/messageIdToString.ts +8 -0
  155. package/src/utils/msgIdFn.ts +24 -0
  156. package/src/utils/multiaddr.ts +19 -0
  157. package/src/utils/publishConfig.ts +33 -0
  158. package/src/utils/set.ts +43 -0
  159. package/src/utils/shuffle.ts +21 -0
  160. package/src/utils/time-cache.ts +71 -0
@@ -0,0 +1,215 @@
1
+ import type { PeerScoreParams } from './peer-score-params.js'
2
+ import type { PeerStats } from './peer-stats.js'
3
+
4
+ type TopicLabel = string
5
+ type TopicStr = string
6
+ type TopicStrToLabel = Map<TopicStr, TopicLabel>
7
+
8
+ export interface TopicScoreWeights<T> {
9
+ p1w: T
10
+ p2w: T
11
+ p3w: T
12
+ p3bw: T
13
+ p4w: T
14
+ }
15
+ export interface ScoreWeights<T> {
16
+ byTopic: Map<TopicLabel, TopicScoreWeights<T>>
17
+ p5w: T
18
+ p6w: T
19
+ p7w: T
20
+ score: T
21
+ }
22
+
23
+ export function computeScoreWeights (
24
+ peer: string,
25
+ pstats: PeerStats,
26
+ params: PeerScoreParams,
27
+ peerIPs: Map<string, Set<string>>,
28
+ topicStrToLabel: TopicStrToLabel
29
+ ): ScoreWeights<number> {
30
+ let score = 0
31
+
32
+ const byTopic = new Map<TopicLabel, TopicScoreWeights<number>>()
33
+
34
+ // topic stores
35
+ Object.entries(pstats.topics).forEach(([topic, tstats]) => {
36
+ // the topic parameters
37
+ // Aggregate by known topicLabel or throw to 'unknown'. This prevent too high cardinality
38
+ const topicLabel = topicStrToLabel.get(topic) ?? 'unknown'
39
+ const topicParams = params.topics[topic]
40
+ if (topicParams === undefined) {
41
+ // we are not scoring this topic
42
+ return
43
+ }
44
+
45
+ let topicScores = byTopic.get(topicLabel)
46
+ if (topicScores == null) {
47
+ topicScores = {
48
+ p1w: 0,
49
+ p2w: 0,
50
+ p3w: 0,
51
+ p3bw: 0,
52
+ p4w: 0
53
+ }
54
+ byTopic.set(topicLabel, topicScores)
55
+ }
56
+
57
+ let p1w = 0
58
+ let p2w = 0
59
+ let p3w = 0
60
+ let p3bw = 0
61
+ let p4w = 0
62
+
63
+ // P1: time in Mesh
64
+ if (tstats.inMesh) {
65
+ const p1 = Math.max(tstats.meshTime / topicParams.timeInMeshQuantum, topicParams.timeInMeshCap)
66
+ p1w += p1 * topicParams.timeInMeshWeight
67
+ }
68
+
69
+ // P2: first message deliveries
70
+ let p2 = tstats.firstMessageDeliveries
71
+ if (p2 > topicParams.firstMessageDeliveriesCap) {
72
+ p2 = topicParams.firstMessageDeliveriesCap
73
+ }
74
+ p2w += p2 * topicParams.firstMessageDeliveriesWeight
75
+
76
+ // P3: mesh message deliveries
77
+ if (
78
+ tstats.meshMessageDeliveriesActive &&
79
+ tstats.meshMessageDeliveries < topicParams.meshMessageDeliveriesThreshold
80
+ ) {
81
+ const deficit = topicParams.meshMessageDeliveriesThreshold - tstats.meshMessageDeliveries
82
+ const p3 = deficit * deficit
83
+ p3w += p3 * topicParams.meshMessageDeliveriesWeight
84
+ }
85
+
86
+ // P3b:
87
+ // NOTE: the weight of P3b is negative (validated in validateTopicScoreParams) so this detracts
88
+ const p3b = tstats.meshFailurePenalty
89
+ p3bw += p3b * topicParams.meshFailurePenaltyWeight
90
+
91
+ // P4: invalid messages
92
+ // NOTE: the weight of P4 is negative (validated in validateTopicScoreParams) so this detracts
93
+ const p4 = tstats.invalidMessageDeliveries * tstats.invalidMessageDeliveries
94
+ p4w += p4 * topicParams.invalidMessageDeliveriesWeight
95
+
96
+ // update score, mixing with topic weight
97
+ score += (p1w + p2w + p3w + p3bw + p4w) * topicParams.topicWeight
98
+
99
+ topicScores.p1w += p1w
100
+ topicScores.p2w += p2w
101
+ topicScores.p3w += p3w
102
+ topicScores.p3bw += p3bw
103
+ topicScores.p4w += p4w
104
+ })
105
+
106
+ // apply the topic score cap, if any
107
+ if (params.topicScoreCap > 0 && score > params.topicScoreCap) {
108
+ score = params.topicScoreCap
109
+
110
+ // Proportionally apply cap to all individual contributions
111
+ const capF = params.topicScoreCap / score
112
+ for (const ws of byTopic.values()) {
113
+ ws.p1w *= capF
114
+ ws.p2w *= capF
115
+ ws.p3w *= capF
116
+ ws.p3bw *= capF
117
+ ws.p4w *= capF
118
+ }
119
+ }
120
+
121
+ let p5w = 0
122
+ let p6w = 0
123
+ let p7w = 0
124
+
125
+ // P5: application-specific score
126
+ const p5 = params.appSpecificScore(peer)
127
+ p5w += p5 * params.appSpecificWeight
128
+
129
+ // P6: IP colocation factor
130
+ pstats.knownIPs.forEach((ip) => {
131
+ if (params.IPColocationFactorWhitelist.has(ip)) {
132
+ return
133
+ }
134
+
135
+ // P6 has a cliff (IPColocationFactorThreshold)
136
+ // It's only applied if at least that many peers are connected to us from that source IP addr.
137
+ // It is quadratic, and the weight is negative (validated in validatePeerScoreParams)
138
+ const peersInIP = peerIPs.get(ip)
139
+ const numPeersInIP = (peersInIP != null) ? peersInIP.size : 0
140
+ if (numPeersInIP > params.IPColocationFactorThreshold) {
141
+ const surplus = numPeersInIP - params.IPColocationFactorThreshold
142
+ const p6 = surplus * surplus
143
+ p6w += p6 * params.IPColocationFactorWeight
144
+ }
145
+ })
146
+
147
+ // P7: behavioural pattern penalty
148
+ const p7 = pstats.behaviourPenalty * pstats.behaviourPenalty
149
+ p7w += p7 * params.behaviourPenaltyWeight
150
+
151
+ score += p5w + p6w + p7w
152
+
153
+ return {
154
+ byTopic,
155
+ p5w,
156
+ p6w,
157
+ p7w,
158
+ score
159
+ }
160
+ }
161
+
162
+ export function computeAllPeersScoreWeights (
163
+ peerIdStrs: Iterable<string>,
164
+ peerStats: Map<string, PeerStats>,
165
+ params: PeerScoreParams,
166
+ peerIPs: Map<string, Set<string>>,
167
+ topicStrToLabel: TopicStrToLabel
168
+ ): ScoreWeights<number[]> {
169
+ const sw: ScoreWeights<number[]> = {
170
+ byTopic: new Map(),
171
+ p5w: [],
172
+ p6w: [],
173
+ p7w: [],
174
+ score: []
175
+ }
176
+
177
+ for (const peerIdStr of peerIdStrs) {
178
+ const pstats = peerStats.get(peerIdStr)
179
+ if (pstats != null) {
180
+ const swPeer = computeScoreWeights(peerIdStr, pstats, params, peerIPs, topicStrToLabel)
181
+
182
+ for (const [topic, swPeerTopic] of swPeer.byTopic) {
183
+ let swTopic = sw.byTopic.get(topic)
184
+ if (swTopic == null) {
185
+ swTopic = {
186
+ p1w: [],
187
+ p2w: [],
188
+ p3w: [],
189
+ p3bw: [],
190
+ p4w: []
191
+ }
192
+ sw.byTopic.set(topic, swTopic)
193
+ }
194
+
195
+ swTopic.p1w.push(swPeerTopic.p1w)
196
+ swTopic.p2w.push(swPeerTopic.p2w)
197
+ swTopic.p3w.push(swPeerTopic.p3w)
198
+ swTopic.p3bw.push(swPeerTopic.p3bw)
199
+ swTopic.p4w.push(swPeerTopic.p4w)
200
+ }
201
+
202
+ sw.p5w.push(swPeer.p5w)
203
+ sw.p6w.push(swPeer.p6w)
204
+ sw.p7w.push(swPeer.p7w)
205
+ sw.score.push(swPeer.score)
206
+ } else {
207
+ sw.p5w.push(0)
208
+ sw.p6w.push(0)
209
+ sw.p7w.push(0)
210
+ sw.score.push(0)
211
+ }
212
+ }
213
+
214
+ return sw
215
+ }
package/src/stream.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { pipe } from '@libp2p/utils'
2
+ import { encode, decode } from 'it-length-prefixed'
3
+ import type { AbortOptions, Stream } from '@libp2p/interface'
4
+ import type { Uint8ArrayList } from 'uint8arraylist'
5
+
6
+ interface OutboundStreamOpts {
7
+ /** Max size in bytes for pushable buffer. If full, will throw on .push */
8
+ maxBufferSize?: number
9
+ }
10
+
11
+ interface InboundStreamOpts {
12
+ /** Max size in bytes for reading messages from the stream */
13
+ maxDataLength?: number
14
+ }
15
+
16
+ export class OutboundStream {
17
+ constructor (private readonly rawStream: Stream, errCallback: (e: Error) => void, opts: OutboundStreamOpts) {
18
+ if (opts.maxBufferSize != null) {
19
+ rawStream.maxWriteBufferLength = opts.maxBufferSize
20
+ }
21
+
22
+ rawStream.addEventListener('close', (evt) => {
23
+ if (evt.error != null) {
24
+ errCallback(evt.error)
25
+ }
26
+ })
27
+ }
28
+
29
+ get protocol (): string {
30
+ return this.rawStream.protocol
31
+ }
32
+
33
+ async push (data: Uint8Array): Promise<void> {
34
+ return this.pushPrefixed(encode.single(data))
35
+ }
36
+
37
+ /**
38
+ * Same to push() but this is prefixed data so no need to encode length prefixed again
39
+ */
40
+ pushPrefixed (data: Uint8ArrayList): void {
41
+ // TODO: backpressure
42
+ this.rawStream.send(data)
43
+ }
44
+
45
+ async close (options?: AbortOptions): Promise<void> {
46
+ await this.rawStream.close(options)
47
+ .catch(err => {
48
+ this.rawStream.abort(err)
49
+ })
50
+ }
51
+ }
52
+
53
+ export class InboundStream {
54
+ public readonly source: AsyncIterable<Uint8ArrayList>
55
+
56
+ private readonly rawStream: Stream
57
+ private readonly closeController: AbortController
58
+
59
+ constructor (rawStream: Stream, opts: InboundStreamOpts = {}) {
60
+ this.rawStream = rawStream
61
+ this.closeController = new AbortController()
62
+
63
+ this.closeController.signal.addEventListener('abort', () => {
64
+ rawStream.close()
65
+ .catch(err => {
66
+ rawStream.abort(err)
67
+ })
68
+ })
69
+
70
+ this.source = pipe(
71
+ this.rawStream,
72
+ (source) => decode(source, opts)
73
+ )
74
+ }
75
+
76
+ async close (): Promise<void> {
77
+ this.closeController.abort()
78
+ }
79
+ }
package/src/tracer.ts ADDED
@@ -0,0 +1,177 @@
1
+ import { RejectReason } from './types.js'
2
+ import type { Metrics } from './metrics.js'
3
+ import type { MsgIdStr, MsgIdToStrFn, PeerIdStr } from './types.js'
4
+
5
+ /**
6
+ * IWantTracer is an internal tracer that tracks IWANT requests in order to penalize
7
+ * peers who don't follow up on IWANT requests after an IHAVE advertisement.
8
+ * The tracking of promises is probabilistic to avoid using too much memory.
9
+ *
10
+ * Note: Do not confuse these 'promises' with JS Promise objects.
11
+ * These 'promises' are merely expectations of a peer's behavior.
12
+ */
13
+ export class IWantTracer {
14
+ /**
15
+ * Promises to deliver a message
16
+ * Map per message id, per peer, promise expiration time
17
+ */
18
+ private readonly promises = new Map<MsgIdStr, Map<PeerIdStr, number>>()
19
+ /**
20
+ * First request time by msgId. Used for metrics to track expire times.
21
+ * Necessary to know if peers are actually breaking promises or simply sending them a bit later
22
+ */
23
+ private readonly requestMsByMsg = new Map<MsgIdStr, number>()
24
+ private readonly requestMsByMsgExpire: number
25
+
26
+ constructor (
27
+ private readonly gossipsubIWantFollowupMs: number,
28
+ private readonly msgIdToStrFn: MsgIdToStrFn,
29
+ private readonly metrics: Metrics | null
30
+ ) {
31
+ this.requestMsByMsgExpire = 10 * gossipsubIWantFollowupMs
32
+ }
33
+
34
+ get size (): number {
35
+ return this.promises.size
36
+ }
37
+
38
+ get requestMsByMsgSize (): number {
39
+ return this.requestMsByMsg.size
40
+ }
41
+
42
+ /**
43
+ * Track a promise to deliver a message from a list of msgIds we are requesting
44
+ */
45
+ addPromise (from: PeerIdStr, msgIds: Uint8Array[]): void {
46
+ // pick msgId randomly from the list
47
+ const ix = Math.floor(Math.random() * msgIds.length)
48
+ const msgId = msgIds[ix]
49
+ const msgIdStr = this.msgIdToStrFn(msgId)
50
+
51
+ let expireByPeer = this.promises.get(msgIdStr)
52
+ if (expireByPeer == null) {
53
+ expireByPeer = new Map()
54
+ this.promises.set(msgIdStr, expireByPeer)
55
+ }
56
+
57
+ const now = Date.now()
58
+
59
+ // If a promise for this message id and peer already exists we don't update the expiry
60
+ if (!expireByPeer.has(from)) {
61
+ expireByPeer.set(from, now + this.gossipsubIWantFollowupMs)
62
+
63
+ if (this.metrics != null) {
64
+ this.metrics.iwantPromiseStarted.inc(1)
65
+ if (!this.requestMsByMsg.has(msgIdStr)) {
66
+ this.requestMsByMsg.set(msgIdStr, now)
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Returns the number of broken promises for each peer who didn't follow up on an IWANT request.
74
+ *
75
+ * This should be called not too often relative to the expire times, since it iterates over the whole data.
76
+ */
77
+ getBrokenPromises (): Map<PeerIdStr, number> {
78
+ const now = Date.now()
79
+ const result = new Map<PeerIdStr, number>()
80
+
81
+ let brokenPromises = 0
82
+
83
+ this.promises.forEach((expireByPeer, msgId) => {
84
+ expireByPeer.forEach((expire, p) => {
85
+ // the promise has been broken
86
+ if (expire < now) {
87
+ // add 1 to result
88
+ result.set(p, (result.get(p) ?? 0) + 1)
89
+ // delete from tracked promises
90
+ expireByPeer.delete(p)
91
+ // for metrics
92
+ brokenPromises++
93
+ }
94
+ })
95
+ // clean up empty promises for a msgId
96
+ if (expireByPeer.size === 0) {
97
+ this.promises.delete(msgId)
98
+ }
99
+ })
100
+
101
+ this.metrics?.iwantPromiseBroken.inc(brokenPromises)
102
+
103
+ return result
104
+ }
105
+
106
+ /**
107
+ * Someone delivered a message, stop tracking promises for it
108
+ */
109
+ deliverMessage (msgIdStr: MsgIdStr, isDuplicate = false): void {
110
+ this.trackMessage(msgIdStr)
111
+
112
+ const expireByPeer = this.promises.get(msgIdStr)
113
+
114
+ // Expired promise, check requestMsByMsg
115
+ if (expireByPeer != null) {
116
+ this.promises.delete(msgIdStr)
117
+
118
+ if (this.metrics != null) {
119
+ this.metrics.iwantPromiseResolved.inc(1)
120
+ if (isDuplicate) { this.metrics.iwantPromiseResolvedFromDuplicate.inc(1) }
121
+ this.metrics.iwantPromiseResolvedPeers.inc(expireByPeer.size)
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * A message got rejected, so we can stop tracking promises and let the score penalty apply from invalid message delivery,
128
+ * unless its an obviously invalid message.
129
+ */
130
+ rejectMessage (msgIdStr: MsgIdStr, reason: RejectReason): void {
131
+ this.trackMessage(msgIdStr)
132
+
133
+ // A message got rejected, so we can stop tracking promises and let the score penalty apply.
134
+ // With the expection of obvious invalid messages
135
+ switch (reason) {
136
+ case RejectReason.Error:
137
+ return
138
+ default:
139
+ break
140
+ }
141
+
142
+ this.promises.delete(msgIdStr)
143
+ }
144
+
145
+ clear (): void {
146
+ this.promises.clear()
147
+ }
148
+
149
+ prune (): void {
150
+ const maxMs = Date.now() - this.requestMsByMsgExpire
151
+ let count = 0
152
+
153
+ for (const [k, v] of this.requestMsByMsg.entries()) {
154
+ if (v < maxMs) {
155
+ // messages that stay too long in the requestMsByMsg map, delete
156
+ this.requestMsByMsg.delete(k)
157
+ count++
158
+ } else {
159
+ // recent messages, keep them
160
+ // sort by insertion order
161
+ break
162
+ }
163
+ }
164
+
165
+ this.metrics?.iwantMessagePruned.inc(count)
166
+ }
167
+
168
+ private trackMessage (msgIdStr: MsgIdStr): void {
169
+ if (this.metrics != null) {
170
+ const requestMs = this.requestMsByMsg.get(msgIdStr)
171
+ if (requestMs !== undefined) {
172
+ this.metrics.iwantPromiseDeliveryTime.observe((Date.now() - requestMs) / 1000)
173
+ this.requestMsByMsg.delete(msgIdStr)
174
+ }
175
+ }
176
+ }
177
+ }
package/src/types.ts ADDED
@@ -0,0 +1,178 @@
1
+ import { TopicValidatorResult } from './index.ts'
2
+ import type { Message } from './index.ts'
3
+ import type { RPC } from './message/rpc.js'
4
+ import type { PrivateKey, PeerId } from '@libp2p/interface'
5
+ import type { Multiaddr } from '@multiformats/multiaddr'
6
+
7
+ export type MsgIdStr = string
8
+ export type PeerIdStr = string
9
+ export type TopicStr = string
10
+ export type IPStr = string
11
+
12
+ export interface AddrInfo {
13
+ id: PeerId
14
+ addrs: Multiaddr[]
15
+ }
16
+
17
+ /**
18
+ * Compute a local non-spec'ed msg-id for faster de-duplication of seen messages.
19
+ * Used exclusively for a local seen_cache
20
+ */
21
+ export interface FastMsgIdFn { (msg: RPC.Message): string | number }
22
+
23
+ /**
24
+ * By default, gossipsub only provide a browser friendly function to convert Uint8Array message id to string.
25
+ * Application could use this option to provide a more efficient function.
26
+ */
27
+ export interface MsgIdToStrFn { (msgId: Uint8Array): string }
28
+
29
+ /**
30
+ * Compute spec'ed msg-id. Used for IHAVE / IWANT messages
31
+ */
32
+ export interface MsgIdFn {
33
+ (msg: Message): Promise<Uint8Array> | Uint8Array
34
+ }
35
+
36
+ export interface DataTransform {
37
+ /**
38
+ * Takes the data published by peers on a topic and transforms the data.
39
+ * Should be the reverse of outboundTransform(). Example:
40
+ * - `inboundTransform()`: decompress snappy payload
41
+ * - `outboundTransform()`: compress snappy payload
42
+ */
43
+ inboundTransform(topic: TopicStr, data: Uint8Array): Uint8Array
44
+
45
+ /**
46
+ * Takes the data to be published (a topic and associated data) transforms the data. The
47
+ * transformed data will then be used to create a `RawGossipsubMessage` to be sent to peers.
48
+ */
49
+ outboundTransform(topic: TopicStr, data: Uint8Array): Uint8Array
50
+ }
51
+
52
+ export enum SignaturePolicy {
53
+ /**
54
+ * On the producing side:
55
+ * - Build messages with the signature, key (from may be enough for certain inlineable public key types), from and seqno fields.
56
+ *
57
+ * On the consuming side:
58
+ * - Enforce the fields to be present, reject otherwise.
59
+ * - Propagate only if the fields are valid and signature can be verified, reject otherwise.
60
+ */
61
+ StrictSign = 'StrictSign',
62
+ /**
63
+ * On the producing side:
64
+ * - Build messages without the signature, key, from and seqno fields.
65
+ * - The corresponding protobuf key-value pairs are absent from the marshalled message, not just empty.
66
+ *
67
+ * On the consuming side:
68
+ * - Enforce the fields to be absent, reject otherwise.
69
+ * - Propagate only if the fields are absent, reject otherwise.
70
+ * - A message_id function will not be able to use the above fields, and should instead rely on the data field. A commonplace strategy is to calculate a hash.
71
+ */
72
+ StrictNoSign = 'StrictNoSign'
73
+ }
74
+
75
+ export interface PublishOpts {
76
+ /**
77
+ * Do not throw `PublishError.NoPeersSubscribedToTopic` error if there are no
78
+ * peers listening on the topic.
79
+ *
80
+ * N.B. if you sent this option to true, and you publish a message on a topic
81
+ * with no peers listening on that topic, no other network node will ever
82
+ * receive the message.
83
+ */
84
+ allowPublishToZeroTopicPeers?: boolean
85
+ ignoreDuplicatePublishError?: boolean
86
+ /** serialize message once and send to all peers without control messages */
87
+ batchPublish?: boolean
88
+ }
89
+
90
+ export enum PublishConfigType {
91
+ Signing,
92
+ Anonymous
93
+ }
94
+
95
+ export type PublishConfig =
96
+ | {
97
+ type: PublishConfigType.Signing
98
+ author: PeerId
99
+ key: Uint8Array
100
+ privateKey: PrivateKey
101
+ }
102
+ | { type: PublishConfigType.Anonymous }
103
+
104
+ export type RejectReasonObj =
105
+ | { reason: RejectReason.Error, error: ValidateError }
106
+ | { reason: Exclude<RejectReason, RejectReason.Error> }
107
+
108
+ export enum RejectReason {
109
+ /**
110
+ * The message failed the configured validation during decoding.
111
+ * SelfOrigin is considered a ValidationError
112
+ */
113
+ Error = 'error',
114
+ /**
115
+ * Custom validator fn reported status IGNORE.
116
+ */
117
+ Ignore = 'ignore',
118
+ /**
119
+ * Custom validator fn reported status REJECT.
120
+ */
121
+ Reject = 'reject',
122
+ /**
123
+ * The peer that sent the message OR the source from field is blacklisted.
124
+ * Causes messages to be ignored, not penalized, neither do score record creation.
125
+ */
126
+ Blacklisted = 'blacklisted'
127
+ }
128
+
129
+ export enum ValidateError {
130
+ /// The message has an invalid signature,
131
+ InvalidSignature = 'invalid_signature',
132
+ /// The sequence number was the incorrect size
133
+ InvalidSeqno = 'invalid_seqno',
134
+ /// The PeerId was invalid
135
+ InvalidPeerId = 'invalid_peerid',
136
+ /// Signature existed when validation has been sent to
137
+ /// [`crate::behaviour::MessageAuthenticity::Anonymous`].
138
+ SignaturePresent = 'signature_present',
139
+ /// Sequence number existed when validation has been sent to
140
+ /// [`crate::behaviour::MessageAuthenticity::Anonymous`].
141
+ SeqnoPresent = 'seqno_present',
142
+ /// Message source existed when validation has been sent to
143
+ /// [`crate::behaviour::MessageAuthenticity::Anonymous`].
144
+ FromPresent = 'from_present',
145
+ /// The data transformation failed.
146
+ TransformFailed = 'transform_failed'
147
+ }
148
+
149
+ export enum MessageStatus {
150
+ duplicate = 'duplicate',
151
+ invalid = 'invalid',
152
+ valid = 'valid'
153
+ }
154
+
155
+ /**
156
+ * Store both Uint8Array and string message id so that we don't have to convert data between the two.
157
+ * See https://github.com/ChainSafe/js-libp2p-gossipsub/pull/274
158
+ */
159
+ export interface MessageId {
160
+ msgId: Uint8Array
161
+ msgIdStr: MsgIdStr
162
+ }
163
+
164
+ /**
165
+ * Typesafe conversion of MessageAcceptance -> RejectReason. TS ensures all values covered
166
+ */
167
+ export function rejectReasonFromAcceptance (
168
+ acceptance: Exclude<TopicValidatorResult, TopicValidatorResult.Accept>
169
+ ): RejectReason.Ignore | RejectReason.Reject {
170
+ switch (acceptance) {
171
+ case TopicValidatorResult.Ignore:
172
+ return RejectReason.Ignore
173
+ case TopicValidatorResult.Reject:
174
+ return RejectReason.Reject
175
+ default:
176
+ throw new Error('Unreachable')
177
+ }
178
+ }