@chainsafe/libp2p-gossipsub
Version:
A typescript implementation of gossipsub
567 lines • 19.7 kB
TypeScript
import { TypedEventEmitter, StrictSign, StrictNoSign, TopicValidatorResult, serviceCapabilities, serviceDependencies } from '@libp2p/interface';
import { type DecodeRPCLimits } from './message/decodeRpc.js';
import { RPC } from './message/rpc.js';
import { MessageCache } from './message-cache.js';
import { type MetricsRegister, type TopicStrToLabel } from './metrics.js';
import { PeerScore, type PeerScoreParams, type PeerScoreThresholds, type PeerScoreStatsDump } from './score/index.js';
import { InboundStream, OutboundStream } from './stream.js';
import { IWantTracer } from './tracer.js';
import { type MsgIdFn, type TopicStr, type MsgIdStr, type PeerIdStr, type FastMsgIdFn, type AddrInfo, type DataTransform, type MsgIdToStrFn, type PublishOpts } from './types.js';
import type { GossipsubOptsSpec } from './config.js';
import type { Direction, PeerId, PeerStore, Message, PublishResult, PubSub, PubSubEvents, PubSubInit, TopicValidatorFn, Logger, ComponentLogger, PrivateKey } from '@libp2p/interface';
import type { ConnectionManager, Registrar } from '@libp2p/interface-internal';
export declare const multicodec: string;
export interface GossipsubOpts extends GossipsubOptsSpec, PubSubInit {
/** if dial should fallback to floodsub */
fallbackToFloodsub: boolean;
/** if self-published messages should be sent to all peers */
floodPublish: boolean;
/** serialize message once and send to all peers without control messages */
batchPublish: boolean;
/** whether PX is enabled; this should be enabled in bootstrappers and other well connected/trusted nodes. */
doPX: boolean;
/** peers with which we will maintain direct connections */
directPeers: AddrInfo[];
/**
* If true will not forward messages to mesh peers until reportMessageValidationResult() is called.
* Messages will be cached in mcache for some time after which they are evicted. Calling
* reportMessageValidationResult() after the message is dropped from mcache won't forward the message.
*/
asyncValidation: boolean;
/**
* Do not throw `PublishError.NoPeersSubscribedToTopic` error if there are no
* peers listening on the topic.
*
* N.B. if you sent this option to true, and you publish a message on a topic
* with no peers listening on that topic, no other network node will ever
* receive the message.
*/
allowPublishToZeroTopicPeers: boolean;
/** Do not throw `PublishError.Duplicate` if publishing duplicate messages */
ignoreDuplicatePublishError: boolean;
/** For a single stream, await processing each RPC before processing the next */
awaitRpcHandler: boolean;
/** For a single RPC, await processing each message before processing the next */
awaitRpcMessageHandler: boolean;
/** message id function */
msgIdFn: MsgIdFn;
/** fast message id function */
fastMsgIdFn: FastMsgIdFn;
/** Uint8Array message id to string function */
msgIdToStrFn: MsgIdToStrFn;
/** override the default MessageCache */
messageCache: MessageCache;
/** peer score parameters */
scoreParams: Partial<PeerScoreParams>;
/** peer score thresholds */
scoreThresholds: Partial<PeerScoreThresholds>;
/** customize GossipsubIWantFollowupTime in order not to apply IWANT penalties */
gossipsubIWantFollowupMs: number;
/** override constants for fine tuning */
prunePeers?: number;
pruneBackoff?: number;
unsubcribeBackoff?: number;
graftFloodThreshold?: number;
opportunisticGraftPeers?: number;
opportunisticGraftTicks?: number;
directConnectTicks?: number;
dataTransform?: DataTransform;
metricsRegister?: MetricsRegister | null;
metricsTopicStrToLabel?: TopicStrToLabel;
/** Prefix tag for debug logs */
debugName?: string;
/**
* Specify the maximum number of inbound gossipsub protocol
* streams that are allowed to be open concurrently
*/
maxInboundStreams?: number;
/**
* Specify the maximum number of outbound gossipsub protocol
* streams that are allowed to be open concurrently
*/
maxOutboundStreams?: number;
/**
* Pass true to run on limited connections - data or time-limited
* connections that may be closed at any time such as circuit relay
* connections.
*
* @default false
*/
runOnLimitedConnection?: boolean;
/**
* Specify max buffer size in bytes for OutboundStream.
* If full it will throw and reject sending any more data.
*/
maxOutboundBufferSize?: number;
/**
* Specify max size to skip decoding messages whose data
* section exceeds this size.
*
*/
maxInboundDataLength?: number;
/**
* If provided, only allow topics in this list
*/
allowedTopics?: string[] | Set<string>;
/**
* Limits to bound protobuf decoding
*/
decodeRpcLimits?: DecodeRPCLimits;
/**
* If true, will utilize the libp2p connection manager tagging system to prune/graft connections to peers, defaults to true
*/
tagMeshPeers: boolean;
/**
* Specify what percent of peers to send gossip to. If the percent results in
* a number smaller than `Dlazy`, `Dlazy` will be used instead.
*
* It should be a number between 0 and 1, with a reasonable default of 0.25
*/
gossipFactor: number;
/**
* The minimum message size in bytes to be considered for sending IDONTWANT messages
*
* @default 512
*/
idontwantMinDataSize?: number;
/**
* The maximum number of IDONTWANT messages per heartbeat per peer
*
* @default 512
*/
idontwantMaxMessages?: number;
}
export interface GossipsubMessage {
propagationSource: PeerId;
msgId: MsgIdStr;
msg: Message;
}
export interface MeshPeer {
peerId: string;
topic: string;
direction: Direction;
}
export interface GossipsubEvents extends PubSubEvents {
'gossipsub:heartbeat': CustomEvent;
'gossipsub:message': CustomEvent<GossipsubMessage>;
'gossipsub:graft': CustomEvent<MeshPeer>;
'gossipsub:prune': CustomEvent<MeshPeer>;
}
interface GossipOptions extends GossipsubOpts {
scoreParams: PeerScoreParams;
scoreThresholds: PeerScoreThresholds;
}
export interface GossipSubComponents {
privateKey: PrivateKey;
peerId: PeerId;
peerStore: PeerStore;
registrar: Registrar;
connectionManager: ConnectionManager;
logger: ComponentLogger;
}
export declare class GossipSub extends TypedEventEmitter<GossipsubEvents> implements PubSub<GossipsubEvents> {
/**
* The signature policy to follow by default
*/
readonly globalSignaturePolicy: typeof StrictSign | typeof StrictNoSign;
multicodecs: string[];
private publishConfig;
private readonly dataTransform;
readonly peers: Map<string, PeerId>;
readonly streamsInbound: Map<string, InboundStream>;
readonly streamsOutbound: Map<string, OutboundStream>;
/** Ensures outbound streams are created sequentially */
private outboundInflightQueue;
/** Direct peers */
readonly direct: Set<string>;
/** Floodsub peers */
private readonly floodsubPeers;
/** Cache of seen messages */
private readonly seenCache;
/**
* Map of peer id and AcceptRequestWhileListEntry
*/
private readonly acceptFromWhitelist;
/**
* Map of topics to which peers are subscribed to
*/
private readonly topics;
/**
* List of our subscriptions
*/
private readonly subscriptions;
/**
* Map of topic meshes
* topic => peer id set
*/
readonly mesh: Map<string, Set<string>>;
/**
* Map of topics to set of peers. These mesh peers are the ones to which we are publishing without a topic membership
* topic => peer id set
*/
readonly fanout: Map<string, Set<string>>;
/**
* Map of last publish time for fanout topics
* topic => last publish time
*/
private readonly fanoutLastpub;
/**
* Map of pending messages to gossip
* peer id => control messages
*/
readonly gossip: Map<string, RPC.ControlIHave[]>;
/**
* Map of control messages
* peer id => control message
*/
readonly control: Map<string, RPC.ControlMessage>;
/**
* Number of IHAVEs received from peer in the last heartbeat
*/
private readonly peerhave;
/** Number of messages we have asked from peer in the last heartbeat */
private readonly iasked;
/** Prune backoff map */
private readonly backoff;
/**
* Connection direction cache, marks peers with outbound connections
* peer id => direction
*/
private readonly outbound;
private readonly msgIdFn;
/**
* A fast message id function used for internal message de-duplication
*/
private readonly fastMsgIdFn;
private readonly msgIdToStrFn;
/** Maps fast message-id to canonical message-id */
private readonly fastMsgIdCache;
/**
* Short term cache for published message ids. This is used for penalizing peers sending
* our own messages back if the messages are anonymous or use a random author.
*/
private readonly publishedMessageIds;
/**
* A message cache that contains the messages for last few heartbeat ticks
*/
private readonly mcache;
/** Peer score tracking */
readonly score: PeerScore;
/**
* Custom validator function per topic.
* Must return or resolve quickly (< 100ms) to prevent causing penalties for late messages.
* If you need to apply validation that may require longer times use `asyncValidation` option and callback the
* validation result through `Gossipsub.reportValidationResult`
*/
readonly topicValidators: Map<string, TopicValidatorFn>;
/**
* Make this protected so child class may want to redirect to its own log.
*/
protected readonly log: Logger;
/**
* Number of heartbeats since the beginning of time
* This allows us to amortize some resource cleanup -- eg: backoff cleanup
*/
private heartbeatTicks;
/**
* Tracks IHAVE/IWANT promises broken by peers
*/
readonly gossipTracer: IWantTracer;
/**
* Tracks IDONTWANT messages received by peers in the current heartbeat
*/
private readonly idontwantCounts;
/**
* Tracks IDONTWANT messages received by peers and the heartbeat they were received in
*
* idontwants are stored for `mcacheLength` heartbeats before being pruned,
* so this map is bounded by peerCount * idontwantMaxMessages * mcacheLength
*/
private readonly idontwants;
private readonly components;
private directPeerInitial;
static multicodec: string;
readonly opts: Required<GossipOptions>;
private readonly decodeRpcLimits;
private readonly metrics;
private status;
private readonly maxInboundStreams?;
private readonly maxOutboundStreams?;
private readonly runOnLimitedConnection?;
private readonly allowedTopics;
private heartbeatTimer;
constructor(components: GossipSubComponents, options?: Partial<GossipsubOpts>);
readonly [Symbol.toStringTag] = "@chainsafe/libp2p-gossipsub";
readonly [serviceCapabilities]: string[];
readonly [serviceDependencies]: string[];
getPeers(): PeerId[];
isStarted(): boolean;
/**
* Mounts the gossipsub protocol onto the libp2p node and sends our
* our subscriptions to every peer connected
*/
start(): Promise<void>;
/**
* Unmounts the gossipsub protocol and shuts down every connection
*/
stop(): Promise<void>;
/** FOR DEBUG ONLY - Dump peer stats for all peers. Data is cloned, safe to mutate */
dumpPeerScoreStats(): PeerScoreStatsDump;
/**
* On an inbound stream opened
*/
private onIncomingStream;
/**
* Registrar notifies an established connection with pubsub protocol
*/
private onPeerConnected;
/**
* Registrar notifies a closing connection with pubsub protocol
*/
private onPeerDisconnected;
private createOutboundStream;
private createInboundStream;
/**
* Add a peer to the router
*/
private addPeer;
/**
* Removes a peer from the router
*/
private removePeer;
get started(): boolean;
/**
* Get a the peer-ids in a topic mesh
*/
getMeshPeers(topic: TopicStr): PeerIdStr[];
/**
* Get a list of the peer-ids that are subscribed to one topic.
*/
getSubscribers(topic: TopicStr): PeerId[];
/**
* Get the list of topics which the peer is subscribed to.
*/
getTopics(): TopicStr[];
/**
* Responsible for processing each RPC message received by other peers.
*/
private pipePeerReadStream;
/**
* Handle error when read stream pipe throws, less of the functional use but more
* to for testing purposes to spy on the error handling
* */
private handlePeerReadStreamError;
/**
* Handles an rpc request from a peer
*/
handleReceivedRpc(from: PeerId, rpc: RPC): Promise<void>;
/**
* Handles a subscription change from a peer
*/
private handleReceivedSubscription;
/**
* Handles a newly received message from an RPC.
* May forward to all peers in the mesh.
*/
private handleReceivedMessage;
/**
* Handles a newly received message from an RPC.
* May forward to all peers in the mesh.
*/
private validateReceivedMessage;
/**
* Return score of a peer.
*/
getScore(peerId: PeerIdStr): number;
/**
* Send an rpc object to a peer with subscriptions
*/
private sendSubscriptions;
/**
* Handles an rpc control message from a peer
*/
private handleControlMessage;
/**
* Whether to accept a message from a peer
*/
acceptFrom(id: PeerIdStr): boolean;
/**
* Handles IHAVE messages
*/
private handleIHave;
/**
* Handles IWANT messages
* Returns messages to send back to peer
*/
private handleIWant;
/**
* Handles Graft messages
*/
private handleGraft;
/**
* Handles Prune messages
*/
private handlePrune;
private handleIdontwant;
/**
* Add standard backoff log for a peer in a topic
*/
private addBackoff;
/**
* Add backoff expiry interval for a peer in a topic
*
* @param id
* @param topic
* @param intervalMs - backoff duration in milliseconds
*/
private doAddBackoff;
/**
* Apply penalties from broken IHAVE/IWANT promises
*/
private applyIwantPenalties;
/**
* Clear expired backoff expiries
*/
private clearBackoff;
/**
* Maybe reconnect to direct peers
*/
private directConnect;
/**
* Maybe attempt connection given signed peer records
*/
private pxConnect;
/**
* Connect to a peer using the gossipsub protocol
*/
private connect;
/**
* Subscribes to a topic
*/
subscribe(topic: TopicStr): void;
/**
* Unsubscribe to a topic
*/
unsubscribe(topic: TopicStr): void;
/**
* Join topic
*/
private join;
/**
* Leave topic
*/
private leave;
private selectPeersToForward;
private selectPeersToPublish;
/**
* Forwards a message from our peers.
*
* For messages published by us (the app layer), this class uses `publish`
*/
private forwardMessage;
/**
* App layer publishes a message to peers, return number of peers this message is published to
* Note: `async` due to crypto only if `StrictSign`, otherwise it's a sync fn.
*
* For messages not from us, this class uses `forwardMessage`.
*/
publish(topic: TopicStr, data: Uint8Array, opts?: PublishOpts): Promise<PublishResult>;
/**
* Send the same data in batch to tosend list without considering cached control messages
* This is not only faster but also avoid allocating memory for each peer
* see https://github.com/ChainSafe/js-libp2p-gossipsub/issues/344
*/
private sendRpcInBatch;
/**
* This function should be called when `asyncValidation` is `true` after
* the message got validated by the caller. Messages are stored in the `mcache` and
* validation is expected to be fast enough that the messages should still exist in the cache.
* There are three possible validation outcomes and the outcome is given in acceptance.
*
* If acceptance = `MessageAcceptance.Accept` the message will get propagated to the
* network. The `propagation_source` parameter indicates who the message was received by and
* will not be forwarded back to that peer.
*
* If acceptance = `MessageAcceptance.Reject` the message will be deleted from the memcache
* and the P₄ penalty will be applied to the `propagationSource`.
*
* If acceptance = `MessageAcceptance.Ignore` the message will be deleted from the memcache
* but no P₄ penalty will be applied.
*
* This function will return true if the message was found in the cache and false if was not
* in the cache anymore.
*
* This should only be called once per message.
*/
reportMessageValidationResult(msgId: MsgIdStr, propagationSource: PeerIdStr, acceptance: TopicValidatorResult): void;
/**
* Sends a GRAFT message to a peer
*/
private sendGraft;
/**
* Sends a PRUNE message to a peer
*/
private sendPrune;
private sendIDontWants;
/**
* Send an rpc object to a peer
*/
private sendRpc;
/** Mutates `outRpc` adding graft and prune control messages */
piggybackControl(id: PeerIdStr, outRpc: RPC, ctrl: RPC.ControlMessage): void;
/** Mutates `outRpc` adding ihave control messages */
private piggybackGossip;
/**
* Send graft and prune messages
*
* @param tograft - peer id => topic[]
* @param toprune - peer id => topic[]
*/
private sendGraftPrune;
/**
* Emits gossip - Send IHAVE messages to a random set of gossip peers
*/
private emitGossip;
/**
* Send gossip messages to GossipFactor peers above threshold with a minimum of D_lazy
* Peers are randomly selected from the heartbeat which exclude mesh + fanout peers
* We also exclude direct peers, as there is no reason to emit gossip to them
*
* @param topic
* @param candidateToGossip - peers to gossip
* @param messageIDs - message ids to gossip
*/
private doEmitGossip;
/**
* Flush gossip and control messages
*/
private flush;
/**
* Adds new IHAVE messages to pending gossip
*/
private pushGossip;
/**
* Make a PRUNE control message for a peer in a topic
*/
private makePrune;
private readonly runHeartbeat;
/**
* Maintains the mesh and fanout maps in gossipsub.
*/
heartbeat(): Promise<void>;
/**
* Given a topic, returns up to count peers subscribed to that topic
* that pass an optional filter function
*
* @param topic
* @param count
* @param filter - a function to filter acceptable peers
*/
private getRandomGossipPeers;
private onScrapeMetrics;
private readonly tagMeshPeer;
private readonly untagMeshPeer;
}
export declare function gossipsub(init?: Partial<GossipsubOpts>): (components: GossipSubComponents) => PubSub<GossipsubEvents>;
export {};
//# sourceMappingURL=index.d.ts.map