diff --git a/packages/libp2p-daemon/README.md b/packages/libp2p-daemon/README.md index db24c0a3..e06d9ac9 100644 --- a/packages/libp2p-daemon/README.md +++ b/packages/libp2p-daemon/README.md @@ -18,7 +18,7 @@ ## Install ```console -$ npm i @libp2p/daemon +$ npm i --location=global @libp2p/daemon ``` ## Specs diff --git a/packages/libp2p-daemon/package.json b/packages/libp2p-daemon/package.json index 9d20e344..b1f2ca9d 100644 --- a/packages/libp2p-daemon/package.json +++ b/packages/libp2p-daemon/package.json @@ -136,11 +136,25 @@ "release": "aegir release" }, "dependencies": { - "@libp2p/daemon-server": "^3.0.0", - "@multiformats/multiaddr": "^11.0.0", + "@chainsafe/libp2p-gossipsub": "^5.0.0", + "@chainsafe/libp2p-noise": "^10.0.0", + "@libp2p/bootstrap": "^5.0.0", + "@libp2p/connection": "^4.0.2", + "@libp2p/crypto": "^1.0.7", + "@libp2p/daemon-server": "^3.0.5", + "@libp2p/floodsub": "^5.0.0", + "@libp2p/kad-dht": "^5.0.1", + "@libp2p/mplex": "^7.0.0", + "@libp2p/peer-id-factory": "^1.0.19", + "@libp2p/peer-record": "^4.0.4", + "@libp2p/peer-store": "^5.0.0", + "@libp2p/pubsub-peer-discovery": "^7.0.0", + "@libp2p/tcp": "^5.0.1", + "@libp2p/websockets": "^5.0.0", + "@multiformats/multiaddr": "^11.0.6", "es-main": "^1.0.2", - "yargs": "^17.3.1", - "yargs-promise": "^1.1.0" + "libp2p": "^0.40.0", + "yargs": "^17.3.1" }, "devDependencies": { "aegir": "^37.2.0", diff --git a/packages/libp2p-daemon/src/index.ts b/packages/libp2p-daemon/src/index.ts index 866fc109..85aef49c 100755 --- a/packages/libp2p-daemon/src/index.ts +++ b/packages/libp2p-daemon/src/index.ts @@ -1,21 +1,16 @@ #! /usr/bin/env node /* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ -import type { Multiaddr } from '@multiformats/multiaddr' import { multiaddr } from '@multiformats/multiaddr' import yargs from 'yargs' -// @ts-expect-error no types -import YargsPromise from 'yargs-promise' -import type { Libp2pServer } from '@libp2p/daemon-server' +import { hideBin } from 'yargs/helpers' import esMain from 'es-main' - -const args = process.argv.slice(2) -const parser = new YargsPromise(yargs) +import server from './server.js' const log = console.log export default async function main (processArgs: string[]) { - parser.yargs + const argv: { [key: string]: any } = yargs(hideBin(processArgs)) .option('listen', { desc: 'daemon control listen multiaddr', type: 'string', @@ -29,13 +24,12 @@ export default async function main (processArgs: string[]) { }) .option('id', { desc: 'peer identity; private key file', - type: 'string', - default: '' + type: 'string' }) .option('hostAddrs', { desc: 'Comma separated list of multiaddrs the host should listen on', type: 'string', - default: '' + default: '/ip4/127.0.0.1/tcp/0' }) .option('announceAddrs', { desc: 'Comma separated list of multiaddrs the host should announce to the network', @@ -51,7 +45,7 @@ export default async function main (processArgs: string[]) { .option('bootstrapPeers', { desc: 'Comma separated list of bootstrap peers; defaults to the IPFS DHT peers', type: 'string', - default: '' + default: '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ,/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN,/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb,/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp,/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa,/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' }) .option('dht', { desc: 'Enables the DHT in full node mode', @@ -64,7 +58,7 @@ export default async function main (processArgs: string[]) { default: false }) .option('nat', { - desc: 'Enables UPnP NAT hole punching', + desc: '(Not yet supported) Enables UPnP NAT hole punching', type: 'boolean', default: false }) @@ -91,29 +85,60 @@ export default async function main (processArgs: string[]) { type: 'string', default: 'gossipsub' }) + .option('pubsubDiscovery', { + desc: 'Enables pubsub peer discovery', + type: 'boolean', + default: false + }) + .option('psk', { + desc: 'Pre-shared key file', + type: 'string' + }) + .option('discoveryInterval', { + desc: 'The interval (ms) to perform peer discovery', + type: 'number', + default: 60e3 + }) + .option('relay', { + desc: 'Enables relay', + type: 'boolean', + default: false + }) + .option('relayHop', { + desc: 'Enables relay HOP', + type: 'boolean', + default: false + }) + .option('relayAdvertise', { + desc: 'Enables realy HOP advertisement', + type: 'boolean', + default: false + }) + .option('relayAuto', { + desc: 'Enables Auto Relay', + type: 'boolean', + default: false + }) + .option('relayAutoListeners', { + desc: 'Maximum number of simultaneous HOP connections for Auto Relay to open', + type: 'number', + default: 2 + }) .fail((msg: string, err: Error | undefined, yargs?: any) => { if (err != null) { throw err // preserve stack } - if (args.length > 0) { + if (hideBin(processArgs).length > 0) { // eslint-disable-next-line log(msg) } yargs.showHelp() }) + .parse() - const { data, argv } = await parser.parse(processArgs) - - if (data != null) { - // Log help and exit - // eslint-disable-next-line - log(data) - process.exit(0) - } - - const daemon = await createLibp2pServer(multiaddr(argv.listen), argv) + const daemon = await server.createLibp2pServer(multiaddr(argv.listen), argv) await daemon.start() if (argv.quiet !== true) { @@ -122,13 +147,6 @@ export default async function main (processArgs: string[]) { } } -export async function createLibp2pServer (listenAddr: Multiaddr, argv: any): Promise { - // const libp2p = await createLibp2p(argv) - // const daemon = await createServer(multiaddr(argv.listen), libp2p) - - throw new Error('Not implemented yet') -} - if (esMain(import.meta)) { main(process.argv) .catch((err) => { diff --git a/packages/libp2p-daemon/src/server.ts b/packages/libp2p-daemon/src/server.ts new file mode 100644 index 00000000..390bfa55 --- /dev/null +++ b/packages/libp2p-daemon/src/server.ts @@ -0,0 +1,121 @@ +import { promises as fs } from 'fs' +import type { Multiaddr } from '@multiformats/multiaddr' +import { createServer, Libp2pServer } from '@libp2p/daemon-server' +import { Libp2p, createLibp2p, Libp2pOptions } from 'libp2p' +import { preSharedKey } from 'libp2p/pnet' +import { noise } from '@chainsafe/libp2p-noise' +import { mplex } from '@libp2p/mplex' +import { tcp } from '@libp2p/tcp' +import { webSockets } from '@libp2p/websockets' +import { bootstrap } from '@libp2p/bootstrap' +import { gossipsub } from '@chainsafe/libp2p-gossipsub' +import { floodsub } from '@libp2p/floodsub' +import { pubsubPeerDiscovery } from '@libp2p/pubsub-peer-discovery' +import { unmarshalPrivateKey } from '@libp2p/crypto/keys' +import { createFromPrivKey } from '@libp2p/peer-id-factory' +import { kadDHT } from '@libp2p/kad-dht' + +export default { + createLibp2pServer: async function (listenAddr: Multiaddr, argv: any): Promise { + // Minimum libp2p setup. + const options: Libp2pOptions = { + addresses: { + listen: argv.hostAddrs.split(','), + announce: argv.announceAddrs.split(',') + }, + + transports: [tcp(), webSockets()], + connectionEncryption: [noise()], + streamMuxers: [mplex()], + peerDiscovery: [] + } + + // Load key file as peer ID. + if (argv.id != null) { + const marshaledKey: Buffer = await fs.readFile(argv.id) + const unmarshaledKey = await unmarshalPrivateKey(marshaledKey) + const peerId = await createFromPrivKey(unmarshaledKey) + + options.peerId = peerId + } + + // Enable bootstrap peers. + if (argv.bootstrap === true && options.peerDiscovery != null) { + options.peerDiscovery.push( + bootstrap({ + timeout: argv.discoveryInterval, + list: argv.bootstrapPeers.split(',') + }) + ) + } + + // Configure PubSub + if (argv.pubsub === true) { + // Router implementation. + switch (argv.pubsubRouter) { + case 'gossipsub': + options.pubsub = gossipsub({ allowPublishToZeroPeers: true }) + break + case 'floodsub': + options.pubsub = floodsub() + break + default: + throw new Error('invalid pubsubRouter type') + } + + // Peer discovery + if (argv.pubsubDiscovery === true && options.peerDiscovery != null) { + const discovery = pubsubPeerDiscovery({ interval: argv.discoveryInterval }) + + // @libp2p/pubsub-peer-discovery at version 7.0.0 seems to have type compatibility problems. + // @ts-expect-error + options.peerDiscovery.push(discovery) + } + } + + // Enable DHT + if (argv.dht === true) { + options.dht = kadDHT() + } + + // Configure PSK + if (argv.psk != null) { + const swarmKey: Buffer = await fs.readFile(argv.psk) + + options.connectionProtector = preSharedKey({ + psk: swarmKey + }) + } + + // Configure relay + if (argv.relay === true) { + options.relay = { + enabled: true + } + + if (argv.relayAuto === true) { + options.relay.autoRelay = { + enabled: true, + maxListeners: argv.relayAutoListeners + } + } + + if (argv.relayHop === true) { + options.relay.hop = { + enabled: true + } + } + + if (argv.relayAdvertise === true) { + options.relay.advertise = { + enabled: true + } + } + } + + const libp2p: Libp2p = await createLibp2p(options) + const daemon: Libp2pServer = createServer(listenAddr, libp2p) + + return daemon + } +} diff --git a/packages/libp2p-daemon/test/cli.spec.ts b/packages/libp2p-daemon/test/cli.spec.ts index 2712cb8d..71a2d013 100644 --- a/packages/libp2p-daemon/test/cli.spec.ts +++ b/packages/libp2p-daemon/test/cli.spec.ts @@ -2,37 +2,40 @@ import { expect } from 'aegir/chai' import sinon from 'sinon' +import { multiaddr } from '@multiformats/multiaddr' import cli from '../src/index.js' +import server from '../src/server.js' -describe.skip('cli', () => { - const daemon = { createDaemon: (options: any) => {} } - +describe('cli', () => { afterEach(() => { sinon.restore() }) it('should create a daemon with default options', async () => { - sinon.stub(daemon, 'createDaemon').callsFake((options) => { + sinon.stub(server, 'createLibp2pServer').callsFake(async (ma, options) => { + const bootstrapPeers: string = '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ,/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN,/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb,/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp,/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa,/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt' + expect(options).to.include({ b: false, bootstrap: false, - 'bootstrap-peers': '', - bootstrapPeers: '', - hostAddrs: '', + 'bootstrap-peers': bootstrapPeers, + bootstrapPeers, + hostAddrs: '/ip4/0.0.0.0/tcp/0', announceAddrs: '', 'conn-mgr': false, connMgr: false, dht: false, 'dht-client': false, dhtClient: false, - id: '', q: false, quiet: false, listen: '/unix/tmp/p2pd.sock' }) + return { - start: () => {}, - stop: () => {} + start: async () => {}, + stop: async () => {}, + getMultiaddr: () => multiaddr() } }) @@ -43,7 +46,7 @@ describe.skip('cli', () => { }) it('should be able to specify options', async () => { - sinon.stub(daemon, 'createDaemon').callsFake((options) => { + sinon.stub(server, 'createLibp2pServer').callsFake(async (ma, options) => { expect(options).to.include({ b: true, bootstrap: true, @@ -61,9 +64,11 @@ describe.skip('cli', () => { quiet: true, listen: '/unix/tmp/d.sock' }) + return { - start: () => {}, - stop: () => {} + start: async () => {}, + stop: async () => {}, + getMultiaddr: () => multiaddr() } }) diff --git a/packages/libp2p-daemon/tsconfig.json b/packages/libp2p-daemon/tsconfig.json index 6b7a2ea4..da56af63 100644 --- a/packages/libp2p-daemon/tsconfig.json +++ b/packages/libp2p-daemon/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "importsNotUsedAsValues": "remove" }, "include": [ "src",