From 825bc6d2dea1157895d885a34b4033e2e62a5fee Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Mon, 23 Oct 2023 09:16:06 -0700 Subject: [PATCH] fix(ipfs): Creating block & data store directories if they don't exit --- README.md | 12 +++-- package.json | 2 +- src/adapters/ipfs/ipfs.js | 58 ++++++++++---------- test/unit/adapters/ipfs.adapter.unit.js | 71 ++++++++++++++++++------- 4 files changed, 89 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 283698f..892ed0f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,13 @@ ## Overview -This is a 'boilerplate' repository. It's intended to be forked to start new projects. However, it can also be run as a stand-alone Circuit Relay, for supporting the PSF network. +This is a 'boilerplate' repository. It's intended to be forked to start new projects. Some code projects that are forks of this repository and regularly pull in changes: +- [pay-to-write database (P2WDB)](https://p2wdb.com/) +- [ipfs-bch-wallet-consumer](https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer) +- [ipfs-bch-wallet-service](https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service) + + +In addition to being forked as a boilerpalte, it can also be run as a stand-alone application to create a [Circuit Relay](https://cashstack.info/docs/local-back-end/circuit-relay), which can support the [PSF](https://psfoundation.info) IPFS network. It can also be used for experimenting with [helia-coord](https://github.com/Permissionless-Software-Foundation/helia-coord) and the [psf-bch-wallet](https://github.com/Permissionless-Software-Foundation/psf-bch-wallet) command-line wallet. ## Boilerplate @@ -12,11 +18,11 @@ This repository has been forked from the [koa-api-boilerplate](https://github.co - [Koa](https://koajs.com/) framework for REST APIs - User management -- Access and rate-limit control using [JWT tokens](https://jwt.io/). +- Access and rate-limit control using [JWT tokens](https://jwt.io/) This boilerplate extends that code to provide the basic features required to be a 'service provider' on the [IPFS](https://ipfs.io) network. This is a core concept in the [web3 Cash Stack](https://cashstack.info). These basic features include: -- [ipfs-coord](https://www.npmjs.com/package/ipfs-coord) for coordinating service providers and consumers across the IPFS network. +- [helia-coord](https://github.com/Permissionless-Software-Foundation/helia-coord) for coordinating service providers and consumers across the IPFS network. - JSON RPC for creating an API between providers and consumers. If you are interested in creating your own service provider on the IPFS network, fork this repository and start building. diff --git a/package.json b/package.json index 2bc8185..71031f7 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "chai": "4.3.0", "coveralls": "3.1.0", "husky": "4.3.8", - "lodash.clonedeep": "^4.5.0", + "lodash.clonedeep": "4.5.0", "mocha": "10.0.0", "semantic-release": "19.0.3", "sinon": "9.2.4", diff --git a/src/adapters/ipfs/ipfs.js b/src/adapters/ipfs/ipfs.js index 2c0945f..cd2459e 100644 --- a/src/adapters/ipfs/ipfs.js +++ b/src/adapters/ipfs/ipfs.js @@ -28,8 +28,9 @@ import config from '../../../config/index.js' // https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/ import * as url from 'url' const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) -console.log('__dirname: ', __dirname) +// console.log('__dirname: ', __dirname) +const ROOT_DIR = `${__dirname}../../../` const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs` class IpfsAdapter { @@ -37,13 +38,7 @@ class IpfsAdapter { // Encapsulate dependencies this.config = config this.fs = fs - // this.create = create - - // Choose the IPFS constructor based on the config settings. - // this.IPFS = IPFSembedded // default - // if (this.config.isProduction) { - // this.IPFS = IPFSexternal - // } + this.createLibp2p = createLibp2p // Properties of this class instance. this.isReady = false @@ -52,11 +47,15 @@ class IpfsAdapter { this.start = this.start.bind(this) this.createNode = this.createNode.bind(this) this.stop = this.stop.bind(this) + this.ensureBlocksDir = this.ensureBlocksDir.bind(this) } // Start an IPFS node. async start () { try { + // Ensure the directory structure exists that is needed by the IPFS node to store data. + this.ensureBlocksDir() + // Create an IPFS node const ipfs = await this.createNode() // console.log('ipfs: ', ipfs) @@ -96,7 +95,7 @@ class IpfsAdapter { const datastore = new FsDatastore(`${IPFS_DIR}/datastore`) // libp2p is the networking layer that underpins Helia - const libp2p = await createLibp2p({ + const libp2p = await this.createLibp2p({ datastore, addresses: { listen: [ @@ -125,8 +124,7 @@ class IpfsAdapter { '/ip4/137.184.13.92/tcp/4001/p2p/12D3KooWMbpo92kSGwWiN6QH7fvstMWiLZKcxmReq3Hhbpya2bq4', '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY', '/ip4/5.161.72.148/tcp/4001/p2p/12D3KooWAVG5kn46P9mjKCLUSPesmmLeKf2a79qRt6u22hJaEARJ', - '/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu', - '/ip4/192.168.2.173/tcp/4001/p2p/12D3KooWAz2hbriyU9o1wHNU2nxqG8mMgAdWnXCSfWiv5kxSfW49' + '/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu' ] }) ], @@ -146,6 +144,8 @@ class IpfsAdapter { return helia } catch (err) { console.error('Error creating Helia node: ', err) + + throw err } } @@ -155,25 +155,23 @@ class IpfsAdapter { return true } - // Remove the '/blocks' directory that is used to store IPFS data. - // Dev Note: It's assumed this node is not pinning any data and that - // everything in this directory is transient. This folder will regularly - // fill up and prevent IPFS from starting. - // rmBlocksDir () { - // try { - // const dir = `${IPFS_DIR}/blocks` - // console.log(`Deleting ${dir} directory...`) - // - // this.fs.rmdirSync(dir, { recursive: true }) - // - // console.log(`${dir} directory is deleted!`) - // - // return true // Signal successful execution. - // } catch (err) { - // console.log('Error in rmBlocksDir()') - // throw err - // } - // } + // Ensure that the directories exist to store blocks from the IPFS network. + 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 + } + } } export default IpfsAdapter diff --git a/test/unit/adapters/ipfs.adapter.unit.js b/test/unit/adapters/ipfs.adapter.unit.js index a0d0aee..70d6b78 100644 --- a/test/unit/adapters/ipfs.adapter.unit.js +++ b/test/unit/adapters/ipfs.adapter.unit.js @@ -94,24 +94,55 @@ describe('#IPFS-adapter', () => { }) }) -// describe('#rmBlocksDir', () => { -// it('should delete the /blocks directory', () => { -// const result = uut.rmBlocksDir() -// -// assert.equal(result, true) -// }) -// -// it('should catch and throw an error', () => { -// try { -// // Force an error -// sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error')) -// -// uut.rmBlocksDir() -// -// assert.fail('Unexpected code path') -// } catch (err) { -// assert.equal(err.message, 'test error') -// } -// }) -// }) + describe('#ensureBlocksDir', () => { + it('should create directory if it does not exist', () => { + // Force desired code path + sandbox.stub(uut.fs, 'existsSync').returns(false) + sandbox.stub(uut.fs, 'mkdirSync').returns(true) + + const result = uut.ensureBlocksDir() + + assert.equal(result, true) + }) + + it('should report and throw errors', () => { + // Force an error + sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error')) + + try { + uut.ensureBlocksDir() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + }) + + describe('#createNode', () => { + it('should report and throw errors', async () => { + // Force an error + sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error')) + + try { + await uut.createNode() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'test error') + } + }) + + it('should create an IPFS node from Helia', async () => { + const result = await uut.createNode() + // console.log('result: ', result) + + // Assert the returned IPFS node has expected properties + assert.property(result, 'libp2p') + assert.property(result, 'blockstore') + + // Stop the IPFS node + await result.stop() + }) + }) })