@chainsafe/libp2p-gossipsub
Version:
A typescript implementation of gossipsub
144 lines • 4.98 kB
JavaScript
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