UNPKG

@chainsafe/libp2p-gossipsub

Version:
144 lines 4.98 kB
export class MessageCache { gossip; msgs = new Map(); msgIdToStrFn; history = []; /** Track with accounting of messages in the mcache that are not yet validated */ notValidatedCount = 0; /** * Holds history of messages in timebounded history arrays */ constructor( /** * The number of indices in the cache history used for gossiping. That means that a message * won't get gossiped anymore when shift got called `gossip` many times after inserting the * message in the cache. */ gossip, historyCapacity, msgIdToStrFn) { this.gossip = gossip; this.msgIdToStrFn = msgIdToStrFn; for (let i = 0; i < historyCapacity; i++) { this.history[i] = []; } } get size() { return this.msgs.size; } /** * Adds a message to the current window and the cache * Returns true if the message is not known and is inserted in the cache */ put(messageId, msg, validated = false) { const { msgIdStr } = messageId; // Don't add duplicate entries to the cache. if (this.msgs.has(msgIdStr)) { return false; } this.msgs.set(msgIdStr, { message: msg, validated, originatingPeers: new Set(), iwantCounts: new Map() }); this.history[0].push({ ...messageId, topic: msg.topic }); if (!validated) { this.notValidatedCount++; } return true; } observeDuplicate(msgId, fromPeerIdStr) { const entry = this.msgs.get(msgId); if ((entry != null) && // if the message is already validated, we don't need to store extra peers sending us // duplicates as the message has already been forwarded !entry.validated) { entry.originatingPeers.add(fromPeerIdStr); } } /** * Retrieves a message from the cache by its ID, if it is still present */ get(msgId) { return this.msgs.get(this.msgIdToStrFn(msgId))?.message; } /** * Increases the iwant count for the given message by one and returns the message together * with the iwant if the message exists. */ getWithIWantCount(msgIdStr, p) { const msg = this.msgs.get(msgIdStr); if (msg == null) { return null; } const count = (msg.iwantCounts.get(p) ?? 0) + 1; msg.iwantCounts.set(p, count); return { msg: msg.message, count }; } /** * Retrieves a list of message IDs for a set of topics */ getGossipIDs(topics) { const msgIdsByTopic = new Map(); for (let i = 0; i < this.gossip; i++) { this.history[i].forEach((entry) => { const msg = this.msgs.get(entry.msgIdStr); if ((msg?.validated ?? false) && topics.has(entry.topic)) { let msgIds = msgIdsByTopic.get(entry.topic); if (msgIds == null) { msgIds = []; msgIdsByTopic.set(entry.topic, msgIds); } msgIds.push(entry.msgId); } }); } return msgIdsByTopic; } /** * Gets a message with msgId and tags it as validated. * This function also returns the known peers that have sent us this message. This is used to * prevent us sending redundant messages to peers who have already propagated it. */ validate(msgId) { const entry = this.msgs.get(msgId); if (entry == null) { return null; } if (!entry.validated) { this.notValidatedCount--; } const { message, originatingPeers } = entry; entry.validated = true; // Clear the known peers list (after a message is validated, it is forwarded and we no // longer need to store the originating peers). entry.originatingPeers = new Set(); return { message, originatingPeers }; } /** * Shifts the current window, discarding messages older than this.history.length of the cache */ shift() { const lastCacheEntries = this.history[this.history.length - 1]; lastCacheEntries.forEach((cacheEntry) => { const entry = this.msgs.get(cacheEntry.msgIdStr); if (entry != null) { this.msgs.delete(cacheEntry.msgIdStr); if (!entry.validated) { this.notValidatedCount--; } } }); this.history.pop(); this.history.unshift([]); } remove(msgId) { const entry = this.msgs.get(msgId); if (entry == null) { return null; } // Keep the message on the history vector, it will be dropped on a shift() this.msgs.delete(msgId); return entry; } } //# sourceMappingURL=message-cache.js.map