@libp2p/floodsub
Version:
libp2p-floodsub, also known as pubsub-flood or just dumbsub, this implementation of pubsub focused on delivering an API for Publish/Subscribe, but with no CastTree Forming (it just floods the network).
298 lines (261 loc) • 7.57 kB
text/typescript
/**
* @packageDocumentation
*
* > Don't use this module
*
* This module is a naive implementation of pubsub. It broadcasts all messages to all network peers, cannot provide older messages and has no protection against bad actors.
*
* It exists for academic purposes only, you should not use it in production.
*
* Instead please use [gossipsub](https://www.npmjs.com/package/@chainsafe/libp2p-gossipsub) - a more complete implementation which is also compatible with floodsub.
*
* @example Configuring libp2p to use floodsub
*
* ```TypeScript
* import { createLibp2p } from 'libp2p'
* import { floodsub } from '@libp2p/floodsub'
*
* const node = await createLibp2p({
* services: {
* pubsub: floodsub()
* }
* //... other options
* })
* await node.start()
*
* node.services.pubsub.subscribe('fruit')
* node.services.pubsub.addEventListener('message', (evt) => {
* console.log(evt)
* })
*
* node.services.pubsub.publish('fruit', new TextEncoder().encode('banana'))
* ```
*/
import { pubSubSymbol } from './constants.ts'
import { FloodSub as FloodSubClass } from './floodsub.ts'
import type { PubSubRPC } from './floodsub.ts'
import type { ComponentLogger, PeerId, PrivateKey, PublicKey, TypedEventTarget } from '@libp2p/interface'
import type { Registrar } from '@libp2p/interface-internal'
export const protocol = '/floodsub/1.0.0'
/**
* On the producing side:
* - Build messages with the signature, key (from may be enough for certain inlineable public key types), from and seqno fields.
*
* On the consuming side:
* - Enforce the fields to be present, reject otherwise.
* - Propagate only if the fields are valid and signature can be verified, reject otherwise.
*/
export const StrictSign = 'StrictSign'
/**
* On the producing side:
* - Build messages without the signature, key, from and seqno fields.
* - The corresponding protobuf key-value pairs are absent from the marshaled message, not just empty.
*
* On the consuming side:
* - Enforce the fields to be absent, reject otherwise.
* - Propagate only if the fields are absent, reject otherwise.
* - 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.
*/
export const StrictNoSign = 'StrictNoSign'
export type SignaturePolicy = typeof StrictSign | typeof StrictNoSign
export interface SignedMessage {
type: 'signed'
from: PeerId
topic: string
data: Uint8Array
sequenceNumber: bigint
signature: Uint8Array
key: PublicKey
}
export interface UnsignedMessage {
type: 'unsigned'
topic: string
data: Uint8Array
}
export type Message = SignedMessage | UnsignedMessage
export interface Subscription {
topic: string
subscribe: boolean
}
export interface SubscriptionChangeData {
peerId: PeerId
subscriptions: Subscription[]
}
export interface FloodSubEvents {
'subscription-change': CustomEvent<SubscriptionChangeData>
message: CustomEvent<Message>
}
export interface PublishResult {
recipients: PeerId[]
}
export enum TopicValidatorResult {
/**
* The message is considered valid, and it should be delivered and forwarded to the network
*/
Accept = 'accept',
/**
* The message is neither delivered nor forwarded to the network
*/
Ignore = 'ignore',
/**
* The message is considered invalid, and it should be rejected
*/
Reject = 'reject'
}
export interface TopicValidatorFn {
(peer: PeerId, message: Message): TopicValidatorResult | Promise<TopicValidatorResult>
}
export interface PeerStreamsEvents {
message: CustomEvent<PubSubRPC>
close: CustomEvent<never>
}
export { pubSubSymbol }
/**
* Returns true if the passed argument is a PubSub implementation
*/
export function isPubSub (obj?: any): obj is FloodSub {
return Boolean(obj?.[pubSubSymbol])
}
export interface FloodSub extends TypedEventTarget<FloodSubEvents> {
/**
* The global signature policy controls whether or not we sill send and receive
* signed or unsigned messages.
*
* Signed messages prevent spoofing message senders and should be preferred to
* using unsigned messages.
*/
globalSignaturePolicy: typeof StrictSign | typeof StrictNoSign
/**
* The protocol name used by FloodSub
*/
protocol: string
/**
* Pubsub routers support message validators per topic, which will validate the message
* before its propagations. They are stored in a map where keys are the topic name and
* values are the validators.
*
* @example
*
* ```TypeScript
* const topic = 'topic'
* const validateMessage = (msgTopic, msg) => {
* const input = uint8ArrayToString(msg.data)
* const validInputs = ['a', 'b', 'c']
*
* if (!validInputs.includes(input)) {
* throw new Error('no valid input received')
* }
* }
* libp2p.pubsub.topicValidators.set(topic, validateMessage)
* ```
*/
topicValidators: Map<string, TopicValidatorFn>
getPeers(): PeerId[]
/**
* Gets a list of topics the node is subscribed to.
*
* ```TypeScript
* const topics = libp2p.pubsub.getTopics()
* ```
*/
getTopics(): string[]
/**
* Subscribes to a pubsub topic.
*
* @example
*
* ```TypeScript
* const topic = 'topic'
* const handler = (msg) => {
* if (msg.topic === topic) {
* // msg.data - pubsub data received
* }
* }
*
* libp2p.pubsub.addEventListener('message', handler)
* libp2p.pubsub.subscribe(topic)
* ```
*/
subscribe(topic: string): void
/**
* Unsubscribes from a pubsub topic.
*
* @example
*
* ```TypeScript
* const topic = 'topic'
* const handler = (msg) => {
* // msg.data - pubsub data received
* }
*
* libp2p.pubsub.removeEventListener(topic handler)
* libp2p.pubsub.unsubscribe(topic)
* ```
*/
unsubscribe(topic: string): void
/**
* Gets a list of the PeerIds that are subscribed to one topic.
*
* @example
*
* ```TypeScript
* const peerIds = libp2p.pubsub.getSubscribers(topic)
* ```
*/
getSubscribers(topic: string): PeerId[]
/**
* Publishes messages to the given topic.
*
* @example
*
* ```TypeScript
* const topic = 'topic'
* const data = uint8ArrayFromString('data')
*
* await libp2p.pubsub.publish(topic, data)
* ```
*/
publish(topic: string, data?: Uint8Array): Promise<PublishResult>
}
export interface FloodSubComponents {
peerId: PeerId
privateKey: PrivateKey
registrar: Registrar
logger: ComponentLogger
}
export interface FloodSubInit {
seenTTL?: number
/**
* Override the protocol registered with the registrar
*
* @default '/floodsub/1.0.0'
*/
protocol?: string
/**
* defines how signatures should be handled
*/
globalSignaturePolicy?: SignaturePolicy
/**
* if can relay messages not subscribed
*/
canRelayMessage?: boolean
/**
* if publish should emit to self, if subscribed
*/
emitSelf?: boolean
/**
* handle this many incoming pubsub messages concurrently
*/
messageProcessingConcurrency?: number
/**
* How many parallel incoming streams to allow on the pubsub protocol per-connection
*/
maxInboundStreams?: number
/**
* How many parallel outgoing streams to allow on the pubsub protocol per-connection
*/
maxOutboundStreams?: number
}
export function floodsub (init: FloodSubInit = {}): (components: FloodSubComponents) => FloodSub {
return (components: FloodSubComponents) => new FloodSubClass(components, init)
}