@helia/bitswap
Version:
JavaScript implementation of the Bitswap data exchange protocol used by Helia
146 lines (128 loc) • 4.42 kB
text/typescript
/* eslint-disable no-loop-func */
import { setMaxListeners } from '@libp2p/interface'
import { anySignal } from 'any-signal'
import { Network } from './network.js'
import { PeerWantLists } from './peer-want-lists/index.js'
import { createBitswapSession } from './session.js'
import { Stats } from './stats.js'
import { WantList } from './want-list.js'
import type { BitswapOptions, Bitswap as BitswapInterface, BitswapWantProgressEvents, BitswapNotifyProgressEvents, WantListEntry, BitswapComponents } from './index.js'
import type { BlockBroker, CreateSessionOptions } from '@helia/interface'
import type { ComponentLogger, PeerId } from '@libp2p/interface'
import type { Logger } from '@libp2p/logger'
import type { AbortOptions } from '@multiformats/multiaddr'
import type { Blockstore } from 'interface-blockstore'
import type { CID } from 'multiformats/cid'
import type { ProgressOptions } from 'progress-events'
export interface WantOptions extends AbortOptions, ProgressOptions<BitswapWantProgressEvents> {
/**
* When searching the routing for providers, stop searching after finding this
* many providers.
*
* @default 3
*/
maxProviders?: number
}
/**
* JavaScript implementation of the Bitswap 'data exchange' protocol
* used by IPFS.
*/
export class Bitswap implements BitswapInterface {
private readonly log: Logger
private readonly logger: ComponentLogger
public readonly stats: Stats
public network: Network
public blockstore: Blockstore
public peerWantLists: PeerWantLists
public wantList: WantList
constructor (components: BitswapComponents, init: BitswapOptions = {}) {
this.logger = components.logger
this.log = components.logger.forComponent('helia:bitswap')
this.blockstore = components.blockstore
// report stats to libp2p metrics
this.stats = new Stats(components)
// the network delivers messages
this.network = new Network(components, init)
// handle which blocks we send to peers
this.peerWantLists = new PeerWantLists({
...components,
network: this.network
}, init)
// handle which blocks we ask peers for
this.wantList = new WantList({
...components,
network: this.network
}, init)
}
createSession (options: CreateSessionOptions = {}): Required<Pick<BlockBroker<BitswapWantProgressEvents>, 'retrieve'>> {
return createBitswapSession({
wantList: this.wantList,
network: this.network,
logger: this.logger
}, options)
}
async want (cid: CID, options: WantOptions = {}): Promise<Uint8Array> {
const controller = new AbortController()
const signal = anySignal([controller.signal, options.signal])
setMaxListeners(Infinity, controller.signal, signal)
// find providers and connect to them
this.network.findAndConnect(cid, {
...options,
signal
})
.catch(err => {
// if the controller was aborted we found the block already so ignore
// the error
if (!controller.signal.aborted) {
this.log.error('error during finding and connect for cid %c', cid, err)
}
})
try {
const result = await this.wantList.wantBlock(cid, {
...options,
signal
})
return result.block
} finally {
// since we have the block we can now abort any outstanding attempts to
// find providers for it
controller.abort()
signal.clear()
}
}
/**
* Sends notifications about the arrival of a block
*/
async notify (cid: CID, block: Uint8Array, options: ProgressOptions<BitswapNotifyProgressEvents> & AbortOptions = {}): Promise<void> {
await Promise.all([
this.peerWantLists.receivedBlock(cid, options),
this.wantList.receivedBlock(cid, options)
])
}
getWantlist (): WantListEntry[] {
return [...this.wantList.wants.values()]
.filter(entry => !entry.cancel)
.map(entry => ({
cid: entry.cid,
priority: entry.priority,
wantType: entry.wantType
}))
}
getPeerWantlist (peer: PeerId): WantListEntry[] | undefined {
return this.peerWantLists.wantListForPeer(peer)
}
/**
* Start the bitswap node
*/
async start (): Promise<void> {
this.wantList.start()
await this.network.start()
}
/**
* Stop the bitswap node
*/
async stop (): Promise<void> {
this.wantList.stop()
await this.network.stop()
}
}