2021-07-06 17:17:26 -07:00
|
|
|
/*
|
|
|
|
|
Clean Architecture Adapter for IPFS.
|
|
|
|
|
This library deals with IPFS so that the apps business logic doesn't need
|
|
|
|
|
to have any specific knowledge of the js-ipfs library.
|
2021-09-05 09:23:38 -07:00
|
|
|
|
|
|
|
|
TODO: Add the external IP address to the list of multiaddrs advertised by
|
|
|
|
|
this node. See this GitHub Issue for details:
|
|
|
|
|
https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38
|
2021-07-06 17:17:26 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Global npm libraries
|
2023-10-22 07:51:45 -07:00
|
|
|
import { createHelia } from 'helia'
|
2022-09-08 08:58:39 -07:00
|
|
|
import fs from 'fs'
|
2023-10-22 07:51:45 -07:00
|
|
|
import { FsBlockstore } from 'blockstore-fs'
|
|
|
|
|
import { FsDatastore } from 'datastore-fs'
|
|
|
|
|
import { createLibp2p } from 'libp2p'
|
|
|
|
|
import { tcp } from '@libp2p/tcp'
|
|
|
|
|
import { noise } from '@chainsafe/libp2p-noise'
|
|
|
|
|
import { yamux } from '@chainsafe/libp2p-yamux'
|
2023-12-04 07:25:32 -08:00
|
|
|
// import { bootstrap } from '@libp2p/bootstrap'
|
2023-12-06 10:20:48 -08:00
|
|
|
import { identify } from '@libp2p/identify'
|
2023-12-06 10:32:59 -08:00
|
|
|
// import { circuitRelayServer, circuitRelayTransport } from 'libp2p/circuit-relay'
|
|
|
|
|
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
2023-10-22 07:51:45 -07:00
|
|
|
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
2023-10-29 16:39:35 -07:00
|
|
|
import { webSockets } from '@libp2p/websockets'
|
2023-12-03 07:28:31 -08:00
|
|
|
import publicIp from 'public-ip'
|
|
|
|
|
import { multiaddr } from '@multiformats/multiaddr'
|
2023-12-03 07:35:47 -08:00
|
|
|
import { webRTC } from '@libp2p/webrtc'
|
2024-01-28 08:03:41 -08:00
|
|
|
import { keychain } from '@libp2p/keychain'
|
|
|
|
|
import { defaultLogger } from '@libp2p/logger'
|
2021-07-06 17:17:26 -07:00
|
|
|
|
|
|
|
|
// Local libraries
|
2022-09-08 08:58:39 -07:00
|
|
|
import config from '../../../config/index.js'
|
2021-07-06 17:17:26 -07:00
|
|
|
|
2023-10-22 17:35:10 -07:00
|
|
|
// Hack to get __dirname back.
|
|
|
|
|
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
|
|
|
|
import * as url from 'url'
|
|
|
|
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
2023-10-23 09:16:06 -07:00
|
|
|
// console.log('__dirname: ', __dirname)
|
2023-10-22 17:35:10 -07:00
|
|
|
|
2023-10-23 09:16:06 -07:00
|
|
|
const ROOT_DIR = `${__dirname}../../../`
|
2023-10-22 17:35:10 -07:00
|
|
|
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
|
2021-12-06 12:46:22 -08:00
|
|
|
|
2021-07-06 17:17:26 -07:00
|
|
|
class IpfsAdapter {
|
|
|
|
|
constructor (localConfig) {
|
|
|
|
|
// Encapsulate dependencies
|
2021-12-29 17:28:35 -08:00
|
|
|
this.config = config
|
2022-09-08 08:58:39 -07:00
|
|
|
this.fs = fs
|
2023-10-23 09:16:06 -07:00
|
|
|
this.createLibp2p = createLibp2p
|
2023-12-03 07:28:31 -08:00
|
|
|
this.publicIp = publicIp
|
|
|
|
|
this.multiaddr = multiaddr
|
2021-07-06 17:17:26 -07:00
|
|
|
|
|
|
|
|
// Properties of this class instance.
|
|
|
|
|
this.isReady = false
|
2023-10-22 17:35:10 -07:00
|
|
|
|
|
|
|
|
// Bind 'this' object to all subfunctions
|
|
|
|
|
this.start = this.start.bind(this)
|
|
|
|
|
this.createNode = this.createNode.bind(this)
|
|
|
|
|
this.stop = this.stop.bind(this)
|
2023-10-23 09:16:06 -07:00
|
|
|
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
2021-07-06 17:17:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start an IPFS node.
|
|
|
|
|
async start () {
|
|
|
|
|
try {
|
2023-10-23 09:16:06 -07:00
|
|
|
// Ensure the directory structure exists that is needed by the IPFS node to store data.
|
|
|
|
|
this.ensureBlocksDir()
|
|
|
|
|
|
2023-10-22 07:51:45 -07:00
|
|
|
// Create an IPFS node
|
|
|
|
|
const ipfs = await this.createNode()
|
|
|
|
|
// console.log('ipfs: ', ipfs)
|
2021-12-29 17:28:35 -08:00
|
|
|
|
2023-12-03 07:28:31 -08:00
|
|
|
this.id = ipfs.libp2p.peerId.toString()
|
|
|
|
|
console.log('IPFS ID: ', this.id)
|
|
|
|
|
|
|
|
|
|
// Attempt to guess our ip4 IP address.
|
|
|
|
|
const ip4 = await this.publicIp.v4()
|
|
|
|
|
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
|
|
|
|
|
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
|
|
|
|
|
|
2023-10-22 07:51:45 -07:00
|
|
|
// Get the multiaddrs for the node.
|
|
|
|
|
const multiaddrs = ipfs.libp2p.getMultiaddrs()
|
2023-12-03 07:28:31 -08:00
|
|
|
multiaddrs.push(detectedMultiaddr)
|
2023-10-22 07:51:45 -07:00
|
|
|
console.log('Multiaddrs: ', multiaddrs)
|
2021-07-06 17:17:26 -07:00
|
|
|
|
2023-10-22 07:51:45 -07:00
|
|
|
this.multiaddrs = multiaddrs
|
2021-09-28 15:44:06 -07:00
|
|
|
|
2021-07-06 17:17:26 -07:00
|
|
|
// Signal that this adapter is ready.
|
|
|
|
|
this.isReady = true
|
|
|
|
|
|
2023-10-22 07:51:45 -07:00
|
|
|
this.ipfs = ipfs
|
|
|
|
|
|
2021-07-06 17:17:26 -07:00
|
|
|
return this.ipfs
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error in ipfs.js/start()')
|
2021-12-06 12:46:22 -08:00
|
|
|
|
|
|
|
|
// If IPFS crashes because the /blocks directory is full, wipe the directory.
|
2022-03-15 18:58:55 -04:00
|
|
|
// if (err.message.includes('No space left on device')) {
|
|
|
|
|
// this.rmBlocksDir()
|
|
|
|
|
// }
|
2021-12-06 12:46:22 -08:00
|
|
|
|
2021-07-06 17:17:26 -07:00
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-08 08:09:48 -07:00
|
|
|
|
2023-10-22 07:51:45 -07:00
|
|
|
// This function creates an IPFS node using Helia.
|
|
|
|
|
// It returns the node as an object.
|
|
|
|
|
async createNode () {
|
|
|
|
|
try {
|
|
|
|
|
// Create block and data stores.
|
|
|
|
|
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
|
|
|
|
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
|
|
|
|
|
2024-01-28 08:22:55 -08:00
|
|
|
// TODO: Replace this with a random string generator.
|
|
|
|
|
const keychainInit = {
|
|
|
|
|
pass: 'very long, very secure password'
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-26 19:52:55 -08:00
|
|
|
// Create an identity
|
2024-01-28 08:03:41 -08:00
|
|
|
let peerId
|
|
|
|
|
const chain = keychain(keychainInit)({
|
|
|
|
|
datastore,
|
|
|
|
|
logger: defaultLogger()
|
|
|
|
|
})
|
2024-01-28 08:19:38 -08:00
|
|
|
try {
|
|
|
|
|
peerId = await chain.exportPeerId('myKey')
|
|
|
|
|
} catch (err) {
|
|
|
|
|
await chain.createKey('myKey', 'RSA', 4096)
|
2024-01-28 08:03:41 -08:00
|
|
|
peerId = await chain.exportPeerId('myKey')
|
|
|
|
|
}
|
2024-01-26 19:52:55 -08:00
|
|
|
|
2023-10-29 16:39:35 -07:00
|
|
|
// Configure services
|
|
|
|
|
const services = {
|
2023-12-06 10:20:48 -08:00
|
|
|
identify: identify(),
|
2023-10-29 16:39:35 -07:00
|
|
|
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
|
|
|
|
}
|
|
|
|
|
if (this.config.isCircuitRelay) {
|
|
|
|
|
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
|
|
|
|
services.relay = circuitRelayServer()
|
|
|
|
|
} else {
|
|
|
|
|
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-22 07:51:45 -07:00
|
|
|
// libp2p is the networking layer that underpins Helia
|
2023-10-23 09:16:06 -07:00
|
|
|
const libp2p = await this.createLibp2p({
|
2024-01-28 08:03:41 -08:00
|
|
|
peerId,
|
2023-10-22 07:51:45 -07:00
|
|
|
datastore,
|
|
|
|
|
addresses: {
|
|
|
|
|
listen: [
|
|
|
|
|
'/ip4/127.0.0.1/tcp/0',
|
2023-10-29 16:39:35 -07:00
|
|
|
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
2023-12-03 07:35:47 -08:00
|
|
|
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
|
|
|
|
|
'/webrtc'
|
2023-10-22 07:51:45 -07:00
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
transports: [
|
2023-10-29 16:39:35 -07:00
|
|
|
tcp(),
|
|
|
|
|
webSockets(),
|
2023-12-03 07:35:47 -08:00
|
|
|
circuitRelayTransport({ discoverRelays: 3 }),
|
|
|
|
|
webRTC()
|
2023-10-22 07:51:45 -07:00
|
|
|
],
|
|
|
|
|
connectionEncryption: [
|
|
|
|
|
noise()
|
|
|
|
|
],
|
|
|
|
|
streamMuxers: [
|
|
|
|
|
yamux()
|
|
|
|
|
],
|
2023-10-29 16:39:35 -07:00
|
|
|
services
|
2023-10-22 07:51:45 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// create a Helia node
|
|
|
|
|
const helia = await createHelia({
|
|
|
|
|
blockstore,
|
|
|
|
|
datastore,
|
|
|
|
|
libp2p
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return helia
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error creating Helia node: ', err)
|
2023-10-23 09:16:06 -07:00
|
|
|
|
|
|
|
|
throw err
|
2023-10-22 07:51:45 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-08 08:09:48 -07:00
|
|
|
async stop () {
|
|
|
|
|
await this.ipfs.stop()
|
2021-09-30 15:20:51 -07:00
|
|
|
|
|
|
|
|
return true
|
2021-08-08 08:09:48 -07:00
|
|
|
}
|
2021-12-06 12:46:22 -08:00
|
|
|
|
2023-10-23 09:16:06 -07:00
|
|
|
// Ensure that the directories exist to store blocks from the IPFS network.
|
2023-10-25 22:13:35 -07:00
|
|
|
// This function is called at startup, before the IPFS node is started.
|
2023-10-23 09:16:06 -07:00
|
|
|
ensureBlocksDir () {
|
|
|
|
|
try {
|
|
|
|
|
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
|
|
|
|
|
|
|
|
|
|
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
|
|
|
|
|
|
|
|
|
|
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
|
|
|
|
|
|
|
|
|
|
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
|
|
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-06 17:17:26 -07:00
|
|
|
}
|
|
|
|
|
|
2022-09-08 08:58:39 -07:00
|
|
|
export default IpfsAdapter
|