import fs from 'fs'; import { createHelia } from 'helia'; import { FsBlockstore } from 'blockstore-fs'; import { createLibp2p } from 'libp2p'; import { gossipsub } from '@chainsafe/libp2p-gossipsub'; import { tcp } from '@libp2p/tcp'; import { noise } from '@chainsafe/libp2p-noise'; import { yamux } from '@chainsafe/libp2p-yamux'; import { identify } from '@libp2p/identify'; import { bootstrap } from '@libp2p/bootstrap'; import type { Libp2p } from 'libp2p'; import { FaultTolerance, PubSub } from '@libp2p/interface'; import { ipfsConfig } from '../config/ipfsConfig'; import { getPrivateKey } from '../utils/crypto'; import { setupServiceDiscovery, stopDiscoveryService } from './discoveryService'; import { createServiceLogger } from '../../utils/logger'; const logger = createServiceLogger('IPFS'); const p2pLogger = createServiceLogger('P2P'); let helia: any; let proxyAgent: any; let libp2pNode: Libp2p; let reconnectInterval: NodeJS.Timeout; export const initIpfsNode = async (externalProxyAgent: any = null) => { try { proxyAgent = externalProxyAgent; const blockstorePath = ipfsConfig.blockstorePath; try { if (!fs.existsSync(blockstorePath)) { fs.mkdirSync(blockstorePath, { recursive: true, mode: 0o755 }); logger.info(`Created blockstore directory: ${blockstorePath}`); } // Check write permissions fs.accessSync(blockstorePath, fs.constants.W_OK); logger.info(`Verified write permissions for blockstore directory: ${blockstorePath}`); } catch (permError: any) { logger.error(`Permission error with blockstore directory: ${blockstorePath}`, permError); throw new Error(`Cannot access or write to blockstore directory: ${permError.message}`); } const blockstore = new FsBlockstore(blockstorePath); const currentNodeIp = process.env.HOSTNAME || ''; logger.info(`Current node public IP: ${currentNodeIp}`); const bootstrapList = getBootstrapList(); logger.info(`Bootstrap peers: ${JSON.stringify(bootstrapList)}`); const bootStrap = bootstrap({ list: bootstrapList, }) as unknown as any; logger.info(`Configuring bootstrap with peers: ${JSON.stringify(bootstrapList)}`); const ipfsPort = ipfsConfig.port; logger.info(`Using port ${ipfsPort} for IPFS/libp2p`); libp2pNode = await createLibp2p({ transports: [tcp()], streamMuxers: [yamux()], connectionEncrypters: [noise()], services: { identify: identify(), pubsub: gossipsub({ allowPublishToZeroTopicPeers: true, emitSelf: false, }), }, peerDiscovery: [bootStrap], addresses: { listen: [`/ip4/0.0.0.0/tcp/${ipfsPort}`], }, transportManager: { faultTolerance: FaultTolerance.NO_FATAL, }, privateKey: await getPrivateKey(), }); p2pLogger.info(`PEER ID: ${libp2pNode.peerId.toString()}`); logger.info( `Listening on: ${libp2pNode .getMultiaddrs() .map((addr: any) => addr.toString()) .join(', ')}`, ); helia = await createHelia({ blockstore, libp2p: libp2pNode, }); const pubsub = libp2pNode.services.pubsub as PubSub; await setupServiceDiscovery(pubsub); setupPeerEventListeners(libp2pNode); connectToSpecificPeers(libp2pNode); return helia; } catch (error) { logger.error('Failed to initialize node:', error); throw error; } }; function getBootstrapList(): string[] { let bootstrapList: string[] = []; bootstrapList = process.env.BOOTSTRAP_NODES?.split(',').map((node) => node.trim()) || []; return bootstrapList; } function setupPeerEventListeners(node: Libp2p) { node.addEventListener('peer:discovery', (event) => { const peerId = event.detail.id.toString(); logger.info(`Discovered peer: ${peerId}`); }); node.addEventListener('peer:connect', (event) => { const peerId = event.detail.toString(); logger.info(`Peer connection succeeded: ${peerId}`); node.peerStore .get(event.detail) .then((peerInfo) => { const multiaddrs = peerInfo?.addresses.map((addr) => addr.multiaddr.toString()) || [ 'unknown', ]; logger.info(`Peer multiaddrs: ${multiaddrs.join(', ')}`); }) .catch((error) => { logger.error(`Error fetching peer info for ${peerId}: ${error.message}`); }); }); node.addEventListener('peer:disconnect', (event) => { const peerId = event.detail.toString(); logger.info(`Disconnected from peer: ${peerId}`); }); node.addEventListener('peer:reconnect-failure', (event) => { const peerId = event.detail.toString(); logger.error(`Peer reconnection failed: ${peerId}`); node.peerStore .get(event.detail) .then((peerInfo) => { const multiaddrs = peerInfo?.addresses.map((addr) => addr.multiaddr.toString()) || [ 'unknown', ]; logger.error(`Peer multiaddrs: ${multiaddrs.join(', ')}`); }) .catch((error) => { logger.error(`Error fetching peer info for ${peerId}: ${error.message}`); }); }); node.addEventListener('connection:close', (event) => { const connection = event.detail; const peerId = connection.remotePeer.toString(); const remoteAddr = connection.remoteAddr.toString(); logger.info(`Connection closed for peer: ${peerId}`); logger.info(`Remote address: ${remoteAddr}`); }); } export const stopIpfsNode = async () => { if (reconnectInterval) { clearInterval(reconnectInterval); } if (libp2pNode) { const pubsub = libp2pNode.services.pubsub as PubSub; await stopDiscoveryService(pubsub); } else { await stopDiscoveryService(null); } if (helia) { await helia.stop(); } }; export const getHeliaInstance = () => { return helia; }; export const getLibp2pInstance = () => { return libp2pNode; }; export const getProxyAgentInstance = () => { return proxyAgent; }; function connectToSpecificPeers(node: Libp2p) { setTimeout(async () => { await attemptPeerConnections(node); reconnectInterval = setInterval(async () => { await attemptPeerConnections(node); }, 120000); }, 5000); } async function attemptPeerConnections(node: Libp2p) { logger.info('Current peer connections:'); const peers = node.getPeers(); if (peers.length === 0) { logger.info(' - No connected peers'); } else { for (const peerId of peers) { try { // Get peer info including addresses const peerInfo = await node.peerStore.get(peerId); const addresses = peerInfo?.addresses.map((addr) => addr.multiaddr.toString()).join(', ') || 'unknown'; logger.info(` - Connected to peer: ${peerId.toString()}`); logger.info(` Addresses: ${addresses}`); } catch (_error) { // Fallback to just showing the peer ID if we can't get address info logger.info(` - Connected to peer: ${peerId.toString()}`); } } } }