Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: make all helia args optional #37

Merged
merged 1 commit into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
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'
}