@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.
- package/README.md +85 -0
- package/dist/index.min.js +19 -0
- package/dist/index.min.js.map +7 -0
- package/dist/src/config.d.ts +32 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +2 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/constants.d.ts +213 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +217 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +15 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/gossipsub.d.ts +419 -0
- package/dist/src/gossipsub.d.ts.map +1 -0
- package/dist/src/gossipsub.js +2520 -0
- package/dist/src/gossipsub.js.map +1 -0
- package/dist/src/index.d.ts +344 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +43 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/message/decodeRpc.d.ts +11 -0
- package/dist/src/message/decodeRpc.d.ts.map +1 -0
- package/dist/src/message/decodeRpc.js +10 -0
- package/dist/src/message/decodeRpc.js.map +1 -0
- package/dist/src/message/index.d.ts +2 -0
- package/dist/src/message/index.d.ts.map +1 -0
- package/dist/src/message/index.js +2 -0
- package/dist/src/message/index.js.map +1 -0
- package/dist/src/message/rpc.d.ts +99 -0
- package/dist/src/message/rpc.d.ts.map +1 -0
- package/dist/src/message/rpc.js +663 -0
- package/dist/src/message/rpc.js.map +1 -0
- package/dist/src/message-cache.d.ts +80 -0
- package/dist/src/message-cache.d.ts.map +1 -0
- package/dist/src/message-cache.js +144 -0
- package/dist/src/message-cache.js.map +1 -0
- package/dist/src/metrics.d.ts +467 -0
- package/dist/src/metrics.d.ts.map +1 -0
- package/dist/src/metrics.js +896 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/score/compute-score.d.ts +4 -0
- package/dist/src/score/compute-score.d.ts.map +1 -0
- package/dist/src/score/compute-score.js +75 -0
- package/dist/src/score/compute-score.js.map +1 -0
- package/dist/src/score/index.d.ts +4 -0
- package/dist/src/score/index.d.ts.map +1 -0
- package/dist/src/score/index.js +4 -0
- package/dist/src/score/index.js.map +1 -0
- package/dist/src/score/message-deliveries.d.ts +45 -0
- package/dist/src/score/message-deliveries.d.ts.map +1 -0
- package/dist/src/score/message-deliveries.js +75 -0
- package/dist/src/score/message-deliveries.js.map +1 -0
- package/dist/src/score/peer-score-params.d.ts +125 -0
- package/dist/src/score/peer-score-params.d.ts.map +1 -0
- package/dist/src/score/peer-score-params.js +159 -0
- package/dist/src/score/peer-score-params.js.map +1 -0
- package/dist/src/score/peer-score-thresholds.d.ts +31 -0
- package/dist/src/score/peer-score-thresholds.d.ts.map +1 -0
- package/dist/src/score/peer-score-thresholds.js +32 -0
- package/dist/src/score/peer-score-thresholds.js.map +1 -0
- package/dist/src/score/peer-score.d.ts +119 -0
- package/dist/src/score/peer-score.d.ts.map +1 -0
- package/dist/src/score/peer-score.js +459 -0
- package/dist/src/score/peer-score.js.map +1 -0
- package/dist/src/score/peer-stats.d.ts +32 -0
- package/dist/src/score/peer-stats.d.ts.map +1 -0
- package/dist/src/score/peer-stats.js +2 -0
- package/dist/src/score/peer-stats.js.map +1 -0
- package/dist/src/score/scoreMetrics.d.ts +23 -0
- package/dist/src/score/scoreMetrics.d.ts.map +1 -0
- package/dist/src/score/scoreMetrics.js +155 -0
- package/dist/src/score/scoreMetrics.js.map +1 -0
- package/dist/src/stream.d.ts +30 -0
- package/dist/src/stream.d.ts.map +1 -0
- package/dist/src/stream.js +55 -0
- package/dist/src/stream.js.map +1 -0
- package/dist/src/tracer.d.ts +53 -0
- package/dist/src/tracer.d.ts.map +1 -0
- package/dist/src/tracer.js +155 -0
- package/dist/src/tracer.js.map +1 -0
- package/dist/src/types.d.ts +148 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +90 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/buildRawMessage.d.ts +20 -0
- package/dist/src/utils/buildRawMessage.d.ts.map +1 -0
- package/dist/src/utils/buildRawMessage.js +151 -0
- package/dist/src/utils/buildRawMessage.js.map +1 -0
- package/dist/src/utils/create-gossip-rpc.d.ts +7 -0
- package/dist/src/utils/create-gossip-rpc.d.ts.map +1 -0
- package/dist/src/utils/create-gossip-rpc.js +31 -0
- package/dist/src/utils/create-gossip-rpc.js.map +1 -0
- package/dist/src/utils/index.d.ts +4 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +4 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/messageIdToString.d.ts +5 -0
- package/dist/src/utils/messageIdToString.d.ts.map +1 -0
- package/dist/src/utils/messageIdToString.js +8 -0
- package/dist/src/utils/messageIdToString.js.map +1 -0
- package/dist/src/utils/msgIdFn.d.ts +10 -0
- package/dist/src/utils/msgIdFn.d.ts.map +1 -0
- package/dist/src/utils/msgIdFn.js +23 -0
- package/dist/src/utils/msgIdFn.js.map +1 -0
- package/dist/src/utils/multiaddr.d.ts +3 -0
- package/dist/src/utils/multiaddr.d.ts.map +1 -0
- package/dist/src/utils/multiaddr.js +15 -0
- package/dist/src/utils/multiaddr.js.map +1 -0
- package/dist/src/utils/publishConfig.d.ts +8 -0
- package/dist/src/utils/publishConfig.d.ts.map +1 -0
- package/dist/src/utils/publishConfig.js +25 -0
- package/dist/src/utils/publishConfig.js.map +1 -0
- package/dist/src/utils/set.d.ts +14 -0
- package/dist/src/utils/set.d.ts.map +1 -0
- package/dist/src/utils/set.js +41 -0
- package/dist/src/utils/set.js.map +1 -0
- package/dist/src/utils/shuffle.d.ts +7 -0
- package/dist/src/utils/shuffle.d.ts.map +1 -0
- package/dist/src/utils/shuffle.js +21 -0
- package/dist/src/utils/shuffle.js.map +1 -0
- package/dist/src/utils/time-cache.d.ts +22 -0
- package/dist/src/utils/time-cache.d.ts.map +1 -0
- package/dist/src/utils/time-cache.js +54 -0
- package/dist/src/utils/time-cache.js.map +1 -0
- package/package.json +142 -0
- package/src/config.ts +31 -0
- package/src/constants.ts +261 -0
- package/src/errors.ts +17 -0
- package/src/gossipsub.ts +3061 -0
- package/src/index.ts +404 -0
- package/src/message/decodeRpc.ts +19 -0
- package/src/message/index.ts +1 -0
- package/src/message/rpc.proto +58 -0
- package/src/message/rpc.ts +848 -0
- package/src/message-cache.ts +196 -0
- package/src/metrics.ts +1014 -0
- package/src/score/compute-score.ts +98 -0
- package/src/score/index.ts +3 -0
- package/src/score/message-deliveries.ts +95 -0
- package/src/score/peer-score-params.ts +316 -0
- package/src/score/peer-score-thresholds.ts +70 -0
- package/src/score/peer-score.ts +565 -0
- package/src/score/peer-stats.ts +33 -0
- package/src/score/scoreMetrics.ts +215 -0
- package/src/stream.ts +79 -0
- package/src/tracer.ts +177 -0
- package/src/types.ts +178 -0
- package/src/utils/buildRawMessage.ts +174 -0
- package/src/utils/create-gossip-rpc.ts +34 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/messageIdToString.ts +8 -0
- package/src/utils/msgIdFn.ts +24 -0
- package/src/utils/multiaddr.ts +19 -0
- package/src/utils/publishConfig.ts +33 -0
- package/src/utils/set.ts +43 -0
- package/src/utils/shuffle.ts +21 -0
- package/src/utils/time-cache.ts +71 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { randomBytes } from '@libp2p/crypto'
|
|
2
|
+
import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'
|
|
3
|
+
import { peerIdFromMultihash } from '@libp2p/peer-id'
|
|
4
|
+
import * as Digest from 'multiformats/hashes/digest'
|
|
5
|
+
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
|
|
6
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
7
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
8
|
+
import { StrictSign, StrictNoSign } from '../index.ts'
|
|
9
|
+
import { RPC } from '../message/rpc.js'
|
|
10
|
+
import { PublishConfigType, ValidateError } from '../types.js'
|
|
11
|
+
import type { Message } from '../index.ts'
|
|
12
|
+
import type { PublishConfig, TopicStr } from '../types.js'
|
|
13
|
+
import type { PublicKey, PeerId } from '@libp2p/interface'
|
|
14
|
+
|
|
15
|
+
export const SignPrefix = uint8ArrayFromString('libp2p-pubsub:')
|
|
16
|
+
|
|
17
|
+
export interface RawMessageAndMessage {
|
|
18
|
+
raw: RPC.Message
|
|
19
|
+
msg: Message
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function buildRawMessage (
|
|
23
|
+
publishConfig: PublishConfig,
|
|
24
|
+
topic: TopicStr,
|
|
25
|
+
originalData: Uint8Array,
|
|
26
|
+
transformedData: Uint8Array
|
|
27
|
+
): Promise<RawMessageAndMessage> {
|
|
28
|
+
switch (publishConfig.type) {
|
|
29
|
+
case PublishConfigType.Signing: {
|
|
30
|
+
const rpcMsg: RPC.Message = {
|
|
31
|
+
from: publishConfig.author.toMultihash().bytes,
|
|
32
|
+
data: transformedData,
|
|
33
|
+
seqno: randomBytes(8),
|
|
34
|
+
topic,
|
|
35
|
+
signature: undefined, // Exclude signature field for signing
|
|
36
|
+
key: undefined // Exclude key field for signing
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Get the message in bytes, and prepend with the pubsub prefix
|
|
40
|
+
// the signature is over the bytes "libp2p-pubsub:<protobuf-message>"
|
|
41
|
+
const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsg)])
|
|
42
|
+
|
|
43
|
+
rpcMsg.signature = await publishConfig.privateKey.sign(bytes)
|
|
44
|
+
rpcMsg.key = publishConfig.key
|
|
45
|
+
|
|
46
|
+
const msg: Message = {
|
|
47
|
+
type: 'signed',
|
|
48
|
+
from: publishConfig.author,
|
|
49
|
+
data: originalData,
|
|
50
|
+
sequenceNumber: BigInt(`0x${uint8ArrayToString(rpcMsg.seqno ?? new Uint8Array(0), 'base16')}`),
|
|
51
|
+
topic,
|
|
52
|
+
signature: rpcMsg.signature,
|
|
53
|
+
key: publicKeyFromProtobuf(rpcMsg.key)
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
raw: rpcMsg,
|
|
57
|
+
msg
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
case PublishConfigType.Anonymous: {
|
|
62
|
+
return {
|
|
63
|
+
raw: {
|
|
64
|
+
from: undefined,
|
|
65
|
+
data: transformedData,
|
|
66
|
+
seqno: undefined,
|
|
67
|
+
topic,
|
|
68
|
+
signature: undefined,
|
|
69
|
+
key: undefined
|
|
70
|
+
},
|
|
71
|
+
msg: {
|
|
72
|
+
type: 'unsigned',
|
|
73
|
+
data: originalData,
|
|
74
|
+
topic
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
throw new Error('Unreachable')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type ValidationResult = { valid: true, message: Message } | { valid: false, error: ValidateError }
|
|
85
|
+
|
|
86
|
+
export async function validateToRawMessage (
|
|
87
|
+
signaturePolicy: typeof StrictNoSign | typeof StrictSign,
|
|
88
|
+
msg: RPC.Message
|
|
89
|
+
): Promise<ValidationResult> {
|
|
90
|
+
// If strict-sign, verify all
|
|
91
|
+
// If anonymous (no-sign), ensure no preven
|
|
92
|
+
|
|
93
|
+
switch (signaturePolicy) {
|
|
94
|
+
case StrictNoSign:
|
|
95
|
+
if (msg.signature != null) { return { valid: false, error: ValidateError.SignaturePresent } }
|
|
96
|
+
if (msg.seqno != null) { return { valid: false, error: ValidateError.SeqnoPresent } }
|
|
97
|
+
if (msg.key != null) { return { valid: false, error: ValidateError.FromPresent } }
|
|
98
|
+
|
|
99
|
+
return { valid: true, message: { type: 'unsigned', topic: msg.topic, data: msg.data ?? new Uint8Array(0) } }
|
|
100
|
+
|
|
101
|
+
case StrictSign: {
|
|
102
|
+
// Verify seqno
|
|
103
|
+
if (msg.seqno == null) { return { valid: false, error: ValidateError.InvalidSeqno } }
|
|
104
|
+
if (msg.seqno.length !== 8) {
|
|
105
|
+
return { valid: false, error: ValidateError.InvalidSeqno }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (msg.signature == null) { return { valid: false, error: ValidateError.InvalidSignature } }
|
|
109
|
+
if (msg.from == null) { return { valid: false, error: ValidateError.InvalidPeerId } }
|
|
110
|
+
|
|
111
|
+
let fromPeerId: PeerId
|
|
112
|
+
try {
|
|
113
|
+
// TODO: Fix PeerId types
|
|
114
|
+
fromPeerId = peerIdFromMultihash(Digest.decode(msg.from))
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return { valid: false, error: ValidateError.InvalidPeerId }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// - check from defined
|
|
120
|
+
// - transform source to PeerId
|
|
121
|
+
// - parse signature
|
|
122
|
+
// - get .key, else from source
|
|
123
|
+
// - check key == source if present
|
|
124
|
+
// - verify sig
|
|
125
|
+
|
|
126
|
+
let publicKey: PublicKey
|
|
127
|
+
if (msg.key != null) {
|
|
128
|
+
publicKey = publicKeyFromProtobuf(msg.key)
|
|
129
|
+
// TODO: Should `fromPeerId.pubKey` be optional?
|
|
130
|
+
if (fromPeerId.publicKey !== undefined && !publicKey.equals(fromPeerId.publicKey)) {
|
|
131
|
+
return { valid: false, error: ValidateError.InvalidPeerId }
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
if (fromPeerId.publicKey == null) {
|
|
135
|
+
return { valid: false, error: ValidateError.InvalidPeerId }
|
|
136
|
+
}
|
|
137
|
+
publicKey = fromPeerId.publicKey
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const rpcMsgPreSign: RPC.Message = {
|
|
141
|
+
from: msg.from,
|
|
142
|
+
data: msg.data,
|
|
143
|
+
seqno: msg.seqno,
|
|
144
|
+
topic: msg.topic,
|
|
145
|
+
signature: undefined, // Exclude signature field for signing
|
|
146
|
+
key: undefined // Exclude key field for signing
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Get the message in bytes, and prepend with the pubsub prefix
|
|
150
|
+
// the signature is over the bytes "libp2p-pubsub:<protobuf-message>"
|
|
151
|
+
const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsgPreSign)])
|
|
152
|
+
|
|
153
|
+
if (!(await publicKey.verify(bytes, msg.signature))) {
|
|
154
|
+
return { valid: false, error: ValidateError.InvalidSignature }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
valid: true,
|
|
159
|
+
message: {
|
|
160
|
+
type: 'signed',
|
|
161
|
+
from: fromPeerId,
|
|
162
|
+
data: msg.data ?? new Uint8Array(0),
|
|
163
|
+
sequenceNumber: BigInt(`0x${uint8ArrayToString(msg.seqno, 'base16')}`),
|
|
164
|
+
topic: msg.topic,
|
|
165
|
+
signature: msg.signature,
|
|
166
|
+
key: msg.key != null ? publicKeyFromProtobuf(msg.key) : publicKey
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
default:
|
|
172
|
+
throw new Error('Unreachable')
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RPC } from '../message/rpc.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a gossipsub RPC object
|
|
5
|
+
*/
|
|
6
|
+
export function createGossipRpc (messages: RPC.Message[] = [], control?: Partial<RPC.ControlMessage>): RPC {
|
|
7
|
+
return {
|
|
8
|
+
subscriptions: [],
|
|
9
|
+
messages,
|
|
10
|
+
control: control !== undefined
|
|
11
|
+
? {
|
|
12
|
+
graft: control.graft ?? [],
|
|
13
|
+
prune: control.prune ?? [],
|
|
14
|
+
ihave: control.ihave ?? [],
|
|
15
|
+
iwant: control.iwant ?? [],
|
|
16
|
+
idontwant: control.idontwant ?? []
|
|
17
|
+
}
|
|
18
|
+
: undefined
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ensureControl (rpc: RPC): Required<RPC> {
|
|
23
|
+
if (rpc.control === undefined) {
|
|
24
|
+
rpc.control = {
|
|
25
|
+
graft: [],
|
|
26
|
+
prune: [],
|
|
27
|
+
ihave: [],
|
|
28
|
+
iwant: [],
|
|
29
|
+
idontwant: []
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return rpc as Required<RPC>
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { msgId } from '@libp2p/pubsub/utils'
|
|
2
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
|
3
|
+
import type { Message } from '../index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a message id, based on the `key` and `seqno`
|
|
7
|
+
*/
|
|
8
|
+
export function msgIdFnStrictSign (msg: Message): Uint8Array {
|
|
9
|
+
if (msg.type !== 'signed') {
|
|
10
|
+
throw new Error('expected signed message type')
|
|
11
|
+
}
|
|
12
|
+
// Should never happen
|
|
13
|
+
if (msg.sequenceNumber == null) { throw Error('missing seqno field') }
|
|
14
|
+
|
|
15
|
+
// TODO: Should use .from here or key?
|
|
16
|
+
return msgId(msg.from.publicKey ?? msg.key, msg.sequenceNumber)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate a message id, based on message `data`
|
|
21
|
+
*/
|
|
22
|
+
export async function msgIdFnStrictNoSign (msg: Message): Promise<Uint8Array> {
|
|
23
|
+
return sha256.encode(msg.data)
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getNetConfig, isNetworkAddress } from '@libp2p/utils'
|
|
2
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
3
|
+
|
|
4
|
+
export function multiaddrToIPStr (multiaddr: Multiaddr): string | null {
|
|
5
|
+
if (isNetworkAddress(multiaddr)) {
|
|
6
|
+
const config = getNetConfig(multiaddr)
|
|
7
|
+
|
|
8
|
+
switch (config.type) {
|
|
9
|
+
case 'ip4':
|
|
10
|
+
case 'ip6':
|
|
11
|
+
|
|
12
|
+
return config.host
|
|
13
|
+
default:
|
|
14
|
+
break
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { publicKeyToProtobuf } from '@libp2p/crypto/keys'
|
|
2
|
+
import { StrictSign, StrictNoSign } from '../index.ts'
|
|
3
|
+
import { PublishConfigType } from '../types.js'
|
|
4
|
+
import type { PublishConfig } from '../types.js'
|
|
5
|
+
import type { PeerId, PrivateKey } from '@libp2p/interface'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prepare a PublishConfig object from a PeerId.
|
|
9
|
+
*/
|
|
10
|
+
export function getPublishConfigFromPeerId (
|
|
11
|
+
signaturePolicy: typeof StrictSign | typeof StrictNoSign,
|
|
12
|
+
peerId: PeerId,
|
|
13
|
+
privateKey: PrivateKey
|
|
14
|
+
): PublishConfig {
|
|
15
|
+
switch (signaturePolicy) {
|
|
16
|
+
case StrictSign: {
|
|
17
|
+
return {
|
|
18
|
+
type: PublishConfigType.Signing,
|
|
19
|
+
author: peerId,
|
|
20
|
+
key: publicKeyToProtobuf(privateKey.publicKey),
|
|
21
|
+
privateKey
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
case StrictNoSign:
|
|
26
|
+
return {
|
|
27
|
+
type: PublishConfigType.Anonymous
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
default:
|
|
31
|
+
throw new Error(`Unknown signature policy "${signaturePolicy}"`)
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/utils/set.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exclude up to `ineed` items from a set if item meets condition `cond`
|
|
3
|
+
*/
|
|
4
|
+
export function removeItemsFromSet<T> (
|
|
5
|
+
superSet: Set<T>,
|
|
6
|
+
ineed: number,
|
|
7
|
+
cond: (peer: T) => boolean = () => true
|
|
8
|
+
): Set<T> {
|
|
9
|
+
const subset = new Set<T>()
|
|
10
|
+
if (ineed <= 0) { return subset }
|
|
11
|
+
|
|
12
|
+
for (const id of superSet) {
|
|
13
|
+
if (subset.size >= ineed) { break }
|
|
14
|
+
if (cond(id)) {
|
|
15
|
+
subset.add(id)
|
|
16
|
+
superSet.delete(id)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return subset
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Exclude up to `ineed` items from a set
|
|
25
|
+
*/
|
|
26
|
+
export function removeFirstNItemsFromSet<T> (superSet: Set<T>, ineed: number): Set<T> {
|
|
27
|
+
return removeItemsFromSet(superSet, ineed, () => true)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class MapDef<K, V> extends Map<K, V> {
|
|
31
|
+
constructor (private readonly getDefault: () => V) {
|
|
32
|
+
super()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getOrDefault (key: K): V {
|
|
36
|
+
let value = super.get(key)
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
value = this.getDefault()
|
|
39
|
+
this.set(key, value)
|
|
40
|
+
}
|
|
41
|
+
return value
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pseudo-randomly shuffles an array
|
|
3
|
+
*
|
|
4
|
+
* Mutates the input array
|
|
5
|
+
*/
|
|
6
|
+
export function shuffle<T> (arr: T[]): T[] {
|
|
7
|
+
if (arr.length <= 1) {
|
|
8
|
+
return arr
|
|
9
|
+
}
|
|
10
|
+
const randInt = (): number => {
|
|
11
|
+
return Math.floor(Math.random() * Math.floor(arr.length))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < arr.length; i++) {
|
|
15
|
+
const j = randInt()
|
|
16
|
+
const tmp = arr[i]
|
|
17
|
+
arr[i] = arr[j]
|
|
18
|
+
arr[j] = tmp
|
|
19
|
+
}
|
|
20
|
+
return arr
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
interface SimpleTimeCacheOpts {
|
|
2
|
+
validityMs: number
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface CacheValue<T> {
|
|
6
|
+
value: T
|
|
7
|
+
validUntilMs: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js
|
|
12
|
+
* for our own need, we don't use lodash throttle to improve performance.
|
|
13
|
+
* This gives 4x - 5x performance gain compared to npm TimeCache
|
|
14
|
+
*/
|
|
15
|
+
export class SimpleTimeCache<T> {
|
|
16
|
+
private readonly entries = new Map<string | number, CacheValue<T>>()
|
|
17
|
+
private readonly validityMs: number
|
|
18
|
+
|
|
19
|
+
constructor (opts: SimpleTimeCacheOpts) {
|
|
20
|
+
this.validityMs = opts.validityMs
|
|
21
|
+
|
|
22
|
+
// allow negative validityMs so that this does not cache anything, spec test compliance.spec.js
|
|
23
|
+
// sends duplicate messages and expect peer to receive all. Application likely uses positive validityMs
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get size (): number {
|
|
27
|
+
return this.entries.size
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Returns true if there was a key collision and the entry is dropped */
|
|
31
|
+
put (key: string | number, value: T): boolean {
|
|
32
|
+
if (this.entries.has(key)) {
|
|
33
|
+
// Key collisions break insertion order in the entries cache, which break prune logic.
|
|
34
|
+
// prune relies on each iterated entry to have strictly ascending validUntilMs, else it
|
|
35
|
+
// won't prune expired entries and SimpleTimeCache will grow unexpectedly.
|
|
36
|
+
// As of Oct 2022 NodeJS v16, inserting the same key twice with different value does not
|
|
37
|
+
// change the key position in the iterator stream. A unit test asserts this behaviour.
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.entries.set(key, { value, validUntilMs: Date.now() + this.validityMs })
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
prune (): void {
|
|
46
|
+
const now = Date.now()
|
|
47
|
+
|
|
48
|
+
for (const [k, v] of this.entries.entries()) {
|
|
49
|
+
if (v.validUntilMs < now) {
|
|
50
|
+
this.entries.delete(k)
|
|
51
|
+
} else {
|
|
52
|
+
// Entries are inserted with strictly ascending validUntilMs.
|
|
53
|
+
// Stop early to save iterations
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
has (key: string): boolean {
|
|
60
|
+
return this.entries.has(key)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get (key: string | number): T | undefined {
|
|
64
|
+
const value = this.entries.get(key)
|
|
65
|
+
return (value != null) && value.validUntilMs >= Date.now() ? value.value : undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
clear (): void {
|
|
69
|
+
this.entries.clear()
|
|
70
|
+
}
|
|
71
|
+
}
|