Skip to content

Commit

Permalink
fix: make all helia args optional (#37)
Browse files Browse the repository at this point in the history
Default to using in-memory block/datastores if they aren't provided and
a libp2p proxy that will throw if you invoke any of it's methods.
  • Loading branch information
achingbrain authored Feb 24, 2023
1 parent 270bb98 commit d15d76c
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 121 deletions.
2 changes: 1 addition & 1 deletion packages/helia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
"@libp2p/interfaces": "^3.3.1",
"blockstore-core": "^3.0.0",
"cborg": "^1.10.0",
"datastore-core": "^8.0.4",
"interface-blockstore": "^4.0.1",
"interface-datastore": "^7.0.3",
"interface-store": "^3.0.4",
Expand All @@ -167,7 +168,6 @@
"@ipld/dag-json": "^10.0.1",
"@libp2p/websockets": "^5.0.3",
"aegir": "^38.1.0",
"datastore-core": "^8.0.4",
"libp2p": "^0.42.2"
},
"typedoc": {
Expand Down
75 changes: 46 additions & 29 deletions packages/helia/src/helia.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GCOptions, Helia, InfoResponse } from '@helia/interface'
import type { GCOptions, Helia } from '@helia/interface'
import type { Libp2p } from '@libp2p/interface-libp2p'
import type { Datastore } from 'interface-datastore'
import { identity } from 'multiformats/hashes/identity'
Expand All @@ -12,14 +12,16 @@ import { PinsImpl } from './pins.js'
import { assertDatastoreVersionIsCurrent } from './utils/datastore-version.js'
import drain from 'it-drain'
import { CustomProgressEvent } from 'progress-events'
import { MemoryDatastore } from 'datastore-core'
import { MemoryBlockstore } from 'blockstore-core'

export class HeliaImpl implements Helia {
public libp2p: Libp2p
public blockstore: BlockStorage
public datastore: Datastore
public pins: Pins

#bitswap: Bitswap
#bitswap?: Bitswap

constructor (init: HeliaInit) {
const hashers: MultihashHasher[] = [
Expand All @@ -29,52 +31,67 @@ export class HeliaImpl implements Helia {
...(init.hashers ?? [])
]

this.pins = new PinsImpl(init.datastore, init.blockstore, init.dagWalkers ?? [])
const datastore = init.datastore ?? new MemoryDatastore()
const blockstore = init.blockstore ?? new MemoryBlockstore()

this.#bitswap = createBitswap(init.libp2p, init.blockstore, {
hashLoader: {
getHasher: async (codecOrName: string | number) => {
const hasher = hashers.find(hasher => {
return hasher.code === codecOrName || hasher.name === codecOrName
})
// @ts-expect-error incomplete libp2p implementation
const libp2p = init.libp2p ?? new Proxy<Libp2p>({}, {
get (_, prop) {
const noop = (): void => {}
const noops = ['start', 'stop']

if (hasher != null) {
return await Promise.resolve(hasher)
}
if (noops.includes(prop.toString())) {
return noop
}

throw new Error(`Could not load hasher for code/name "${codecOrName}"`)
if (prop === 'isProxy') {
return true
}

throw new Error('Please configure Helia with a libp2p instance')
},
set () {
throw new Error('Please configure Helia with a libp2p instance')
}
})

this.libp2p = init.libp2p
this.blockstore = new BlockStorage(init.blockstore, this.#bitswap, this.pins)
this.datastore = init.datastore
this.pins = new PinsImpl(datastore, blockstore, init.dagWalkers ?? [])

if (init.libp2p != null) {
this.#bitswap = createBitswap(libp2p, blockstore, {
hashLoader: {
getHasher: async (codecOrName: string | number) => {
const hasher = hashers.find(hasher => {
return hasher.code === codecOrName || hasher.name === codecOrName
})

if (hasher != null) {
return await Promise.resolve(hasher)
}

throw new Error(`Could not load hasher for code/name "${codecOrName}"`)
}
}
})
}

this.libp2p = libp2p
this.blockstore = new BlockStorage(blockstore, this.pins, this.#bitswap)
this.datastore = datastore
}

async start (): Promise<void> {
await assertDatastoreVersionIsCurrent(this.datastore)

this.#bitswap.start()
this.#bitswap?.start()
await this.libp2p.start()
}

async stop (): Promise<void> {
this.#bitswap.stop()
this.#bitswap?.stop()
await this.libp2p.stop()
}

async info (): Promise<InfoResponse> {

This comment has been minimized.

Copy link
@andrewzhurov

andrewzhurov Mar 17, 2023

Curious about why it's been removed, my guess would be that it computes a bunch of values, whereas only some of them may be wanted by user, and user can get these values a la carte from the public libp2p field, so seems to be an unnecessary interface.
And tests got simpler!
Ok, kinda answered my own wonderings 😄 (let me know if there are more good reasons behind it), good call removing it!

return {
peerId: this.libp2p.peerId,
multiaddrs: this.libp2p.getMultiaddrs(),
agentVersion: this.libp2p.identifyService.host.agentVersion,
protocolVersion: this.libp2p.identifyService.host.protocolVersion,
protocols: this.libp2p.getProtocols(),
status: this.libp2p.isStarted() ? 'running' : 'stopped'
}
}

async gc (options: GCOptions = {}): Promise<void> {
const releaseLock = await this.blockstore.lock.writeLock()

Expand Down
8 changes: 4 additions & 4 deletions packages/helia/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ export interface HeliaInit {
/**
* A libp2p node is required to perform network operations
*/
libp2p: Libp2p
libp2p?: Libp2p

/**
* The blockstore is where blocks are stored
*/
blockstore: Blockstore
blockstore?: Blockstore

/**
* The datastore is where data is stored
*/
datastore: Datastore
datastore?: Datastore

/**
* By default sha256, sha512 and identity hashes are supported for
Expand All @@ -83,7 +83,7 @@ export interface HeliaInit {
/**
* Create and return a Helia node
*/
export async function createHelia (init: HeliaInit): Promise<Helia> {
export async function createHelia (init: HeliaInit = {}): Promise<Helia> {
const helia = new HeliaImpl(init)

if (init.start !== false) {
Expand Down
39 changes: 22 additions & 17 deletions packages/helia/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ export interface BlockStorageOptions extends AbortOptions {
export class BlockStorage extends BaseBlockstore implements Blockstore {
public lock: Mortice
private readonly child: Blockstore
private readonly bitswap: Bitswap
private readonly bitswap?: Bitswap
private readonly pins: Pins

/**
* Create a new BlockStorage
*/
constructor (blockstore: Blockstore, bitswap: Bitswap, pins: Pins) {
constructor (blockstore: Blockstore, pins: Pins, bitswap?: Bitswap) {
super()

this.child = blockstore
Expand All @@ -54,10 +54,10 @@ export class BlockStorage extends BaseBlockstore implements Blockstore {
* Put a block to the underlying datastore
*/
async put (cid: CID, block: Uint8Array, options: AbortOptions = {}): Promise<void> {
const releaseLock = await this.lock.writeLock()
const releaseLock = await this.lock.readLock()

try {
if (this.bitswap.isStarted()) {
if (this.bitswap?.isStarted() === true) {
await this.bitswap.put(cid, block, options)
} else {
await this.child.put(cid, block, options)
Expand All @@ -71,16 +71,16 @@ export class BlockStorage extends BaseBlockstore implements Blockstore {
* Put a multiple blocks to the underlying datastore
*/
async * putMany (blocks: AwaitIterable<{ key: CID, value: Uint8Array }>, options: AbortOptions = {}): AsyncGenerator<{ key: CID, value: Uint8Array }, void, undefined> {
const releaseLock = await this.lock.writeLock()
const releaseLock = await this.lock.readLock()

try {
const missingBlocks = filter(blocks, async ({ key }) => { return !(await this.child.has(key)) })
const missingBlocks = filter(blocks, async ({ key }) => {
return !(await this.child.has(key))
})

if (this.bitswap.isStarted()) {
yield * this.bitswap.putMany(missingBlocks, options)
} else {
yield * this.child.putMany(missingBlocks, options)
}
const store = this.bitswap?.isStarted() === true ? this.bitswap : this.child

yield * store.putMany(missingBlocks, options)
} finally {
releaseLock()
}
Expand All @@ -93,8 +93,8 @@ export class BlockStorage extends BaseBlockstore implements Blockstore {
const releaseLock = await this.lock.readLock()

try {
if (!(await this.has(cid)) && this.bitswap.isStarted()) {
return await this.bitswap.get(cid, options)
if (!(await this.has(cid)) && this.bitswap?.isStarted() === true) {
return await this.bitswap?.get(cid, options)
} else {
return await this.child.get(cid, options)
}
Expand All @@ -115,7 +115,7 @@ export class BlockStorage extends BaseBlockstore implements Blockstore {

void Promise.resolve().then(async () => {
for await (const cid of cids) {
if (!(await this.has(cid)) && this.bitswap.isStarted()) {
if (!(await this.has(cid)) && this.bitswap?.isStarted() === true) {
getFromBitswap.push(cid)
} else {
getFromChild.push(cid)
Expand All @@ -128,10 +128,15 @@ export class BlockStorage extends BaseBlockstore implements Blockstore {
getFromBitswap.throw(err)
})

yield * merge(
this.bitswap.getMany(getFromBitswap, options),
const streams = [
this.child.getMany(getFromChild, options)
)
]

if (this.bitswap?.isStarted() === true) {
streams.push(this.bitswap.getMany(getFromBitswap, options))
}

yield * merge(...streams)
} finally {
releaseLock()
}
Expand Down
42 changes: 21 additions & 21 deletions packages/helia/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
import { createHelia } from '../src/index.js'
import type { Helia } from '@helia/interface'
import { CID } from 'multiformats/cid'
import { Key } from 'interface-datastore'

describe('helia', () => {
let helia: Helia
Expand Down Expand Up @@ -37,28 +39,11 @@ describe('helia', () => {
})

it('stops and starts', async () => {
const startedInfo = await helia.info()

expect(startedInfo).to.have.property('status', 'running')
expect(startedInfo).to.have.property('protocols')
.with.property('length').that.is.greaterThan(0)
expect(helia.libp2p.isStarted()).to.be.true()

await helia.stop()

const stoppedInfo = await helia.info()

expect(stoppedInfo).to.have.property('status', 'stopped')
expect(stoppedInfo).to.have.property('protocols')
.with.lengthOf(0)
})

it('returns node information', async () => {
const info = await helia.info()

expect(info).to.have.property('peerId').that.is.ok()
expect(info).to.have.property('multiaddrs').that.is.an('array')
expect(info).to.have.property('agentVersion').that.is.a('string')
expect(info).to.have.property('protocolVersion').that.is.a('string')
expect(helia.libp2p.isStarted()).to.be.false()
})

it('should have a blockstore', async () => {
Expand Down Expand Up @@ -92,8 +77,23 @@ describe('helia', () => {
})
})

const info = await helia.info()
expect(helia.libp2p.isStarted()).to.be.false()
})

it('does not require any constructor args', async () => {
const helia = await createHelia()

const cid = CID.parse('QmaQwYWpchozXhFv8nvxprECWBSCEppN9dfd2VQiJfRo3F')
const block = Uint8Array.from([0, 1, 2, 3])
await helia.blockstore.put(cid, block)
await expect(helia.blockstore.has(cid)).to.eventually.be.true()

const key = new Key(`/${cid.toString()}`)
await helia.datastore.put(key, block)
await expect(helia.datastore.has(key)).to.eventually.be.true()

expect(info).to.have.property('status', 'stopped')
expect(() => {
helia.libp2p.isStarted()
}).to.throw('Please configure Helia with a libp2p instance')
})
})
49 changes: 0 additions & 49 deletions packages/interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type { Libp2p } from '@libp2p/interface-libp2p'
import type { Blockstore } from 'interface-blockstore'
import type { AbortOptions } from '@libp2p/interfaces'
import type { PeerId } from '@libp2p/interface-peer-id'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Datastore } from 'interface-datastore'
import type { Pins } from './pins.js'
import type { ProgressEvent, ProgressOptions } from 'progress-events'
Expand Down Expand Up @@ -48,22 +47,6 @@ export interface Helia {
*/
pins: Pins

/**
* Returns information about this node
*
* @example
*
* ```typescript
* import { createHelia } from 'helia'
*
* const node = await createHelia()
* const id = await node.info()
* console.info(id)
* // { peerId: PeerId(12D3Foo), ... }
* ```
*/
info: (options?: InfoOptions) => Promise<InfoResponse>

/**
* Starts the Helia node
*/
Expand Down Expand Up @@ -94,35 +77,3 @@ export interface InfoOptions extends AbortOptions {
*/
peerId?: PeerId
}

export interface InfoResponse {
/**
* The ID of the peer this info is about
*/
peerId: PeerId

/**
* The multiaddrs the peer is listening on
*/
multiaddrs: Multiaddr[]

/**
* The peer's reported agent version
*/
agentVersion: string

/**
* The peer's reported protocol version
*/
protocolVersion: string

/**
* The protocols the peer supports
*/
protocols: string[]

/**
* The status of the node
*/
status: 'running' | 'stopped'
}

0 comments on commit d15d76c

Please sign in to comment.