Compare commits

...
106 Commits
Author SHA1 Message Date
Chris Troutner 7b9bde456e Merge pull request #31 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-06-21 15:59:09 -07:00
Chris Troutner ed0d468c2d Fixing merge conflicts 2022-06-21 15:56:38 -07:00
Chris Troutner fe1b428dac Adding production env var 2022-06-21 15:34:31 -07:00
Chris Troutner 0333f77f5a Fixing broken link in README 2022-06-20 07:30:10 -07:00
Chris Troutner 3c908d1a4d Merge pull request #90 from Permissionless-Software-Foundation/ct-unstable
Increasing test coverage to 100%
2022-06-20 07:22:10 -07:00
Chris Troutner eb46a4c130 fix(tests): Got to 100% test coverage for entire repo 2022-06-20 07:19:36 -07:00
Chris Troutner d0efdcfa07 fix(ipfs-coord): Increased unit test coverage to 100% 2022-06-20 07:01:43 -07:00
Chris Troutner 62a7fcb16d fix(tests): Got bin/server.js unit test coverage to 100% 2022-06-19 20:21:40 -07:00
Chris Troutner d8addd5b27 Trying to build unit tests for bin/server 2022-06-19 20:00:25 -07:00
Chris Troutner bf6f1cf62d Merge pull request #89 from Permissionless-Software-Foundation/ct-unstable
Updating dependencies
2022-06-19 13:25:07 -07:00
Chris Troutner b7df7c47a5 Updated ipfs 2022-06-19 13:23:52 -07:00
Chris Troutner ae260db4af Updated some dependencies 2022-06-19 13:19:31 -07:00
Chris Troutner 778098ca12 fix(ipfs-coord): Updating to v7.1.19 2022-05-28 09:29:31 -07:00
Chris Troutner 0c4bb9c4a1 fix(ipfs-coord): Updating to v7.1.18 2022-05-28 09:09:44 -07:00
Chris Troutner 9cb975656d Merge pull request #88 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.17
2022-05-28 08:00:57 -07:00
Chris Troutner 4f6564423e fix(ipfs-coord): Updating to v7.1.17 2022-05-28 07:59:47 -07:00
Chris Troutner 54e7e74ae5 Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
Subscribing to chat channel at startup
2022-05-26 09:14:50 -07:00
Chris Troutner 324c372119 fix(startup): Subscribing to chat pubsub channel at startup 2022-05-26 09:12:15 -07:00
Chris Troutner da62f1bbe3 linting 2022-05-26 08:24:45 -07:00
Chris Troutner 129047fe88 fix(startup): Announcing config settings at startup 2022-05-26 08:24:30 -07:00
Chris Troutner 3f652688b0 Merge pull request #30 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Using updated image
2022-05-16 15:53:39 -07:00
Chris Troutner 3ccbfdb614 fix(docker): Using updated image 2022-05-16 15:52:34 -07:00
Chris Troutner 5b4d00b949 Merge pull request #29 from Permissionless-Software-Foundation/ct-unstable
fix(docker): mounting startup script on startup
2022-05-16 15:18:42 -07:00
Chris Troutner a666dbcafd fix(docker): mounting startup script on startup 2022-05-16 15:16:48 -07:00
Chris Troutner 3d6d544c21 Merge pull request #28 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Using image rather than dockerfile
2022-05-16 14:51:22 -07:00
Chris Troutner 2ca54865bc fix(docker): Using image rather than dockerfile 2022-05-16 14:18:09 -07:00
Chris Troutner df778d6d8e Merge pull request #27 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider & removing event emitter dependency.
2022-05-16 14:08:55 -07:00
Chris Troutner 3c64f44dfd Reducing screen spam 2022-05-16 14:06:37 -07:00
Chris Troutner 45ea47d4d0 fix(config): Fixing env var setting 2022-05-16 13:56:19 -07:00
Chris Troutner 150e3d6799 Fixing env var 2022-05-16 13:54:04 -07:00
Chris Troutner b3011bcb69 linting 2022-05-16 13:32:46 -07:00
Chris Troutner ccbe9a6707 feat(event emitter): Removing dependency on event emitter 2022-05-16 13:32:16 -07:00
Chris Troutner 23ebdaa7f3 Reducing debug level 2022-05-16 12:35:51 -07:00
Chris Troutner fb182ced58 Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
Updating docker-compose file
2022-05-16 12:33:03 -07:00
Chris Troutner bec7c11e46 Updating README 2022-05-16 12:31:53 -07:00
Chris Troutner fb10e10226 fix(production): Updating docker images 2022-05-16 12:30:58 -07:00
Chris Troutner 5b37e47747 Merge pull request #85 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v6.4.1
2022-05-16 12:15:47 -07:00
Chris Troutner a3cea782ba fix(bch-js): Updating to v6.4.1 2022-05-16 12:14:20 -07:00
Chris Troutner 14ffdae194 Merge pull request #84 from Permissionless-Software-Foundation/dh-test-coverage
feat(coverage): Got ipfs-coord.js to 100% test coverage
2022-05-16 12:09:18 -07:00
Chris Troutner 5792871e0a Merge pull request #26 from Permissionless-Software-Foundation/ct-unstable
Adding utxoIsValid and getTokenData endpoints
2022-05-10 13:19:27 -07:00
Chris Troutner c2ec08f03d feat(getTokenData): Added endpoint for getting data associated with a token 2022-05-10 13:18:24 -07:00
Chris Troutner e4489cfcac linting 2022-05-10 13:06:21 -07:00
Chris Troutner e8c08beeb3 feat(utxoIsValid): Adding endpoint support for utxoIsValid 2022-05-10 13:06:03 -07:00
Chris Troutner 4a64423885 fix(bch-js): Updated to v6.3.4 2022-05-10 11:53:55 -07:00
Daniel Gonzalez 7e3ce1564d feat(coverage): Got ipfs-coord.js to 100% test coverage 2022-03-25 22:12:11 -04:00
Chris Troutner 8154dd96f9 Merge pull request #83 from Permissionless-Software-Foundation/ct-unstable
fix(deps): Updating bch-js
2022-03-20 07:48:42 -07:00
Chris Troutner e37223cc88 fix(deps): Updating bch-js 2022-03-20 07:46:08 -07:00
Chris Troutner 5002ef2b90 Merge pull request #82 from Permissionless-Software-Foundation/dh-test-coverage-ipfsjs
feat(test): Got ipfs.js to 100% test coverage
2022-03-18 11:27:32 -07:00
Chris Troutner 5d95109ad4 more linting 2022-03-18 11:24:15 -07:00
Chris Troutner 13b3150f62 minor linting 2022-03-18 11:24:04 -07:00
Daniel Gonzalez b3a4a7c1ab feat(test): Got ipfs.js to 100% test coverage 2022-03-15 18:58:55 -04:00
Chris Troutner d6b64fce59 Merge pull request #25 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updating IPFS container from v11 to v12
2022-03-15 14:15:22 -07:00
Chris Troutner 8c12222d9c fix(production): Updating IPFS container from v11 to v12 2022-03-15 14:13:22 -07:00
Chris Troutner 26a79d1880 Merge pull request #24 from Permissionless-Software-Foundation/ct-unstable
fix(production): Slight tweeks to docker container
2022-03-15 13:33:12 -07:00
Chris Troutner 3f96b608b5 fix(production): Slight tweeks to docker container 2022-03-15 13:31:00 -07:00
Chris Troutner a669fc6440 Merge pull request #23 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.5
2022-03-10 09:58:44 -08:00
Chris Troutner 42ee03a6d9 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-10 09:56:50 -08:00
Chris Troutner 733e8fd405 Merge pull request #81 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.6
2022-03-10 09:29:32 -08:00
Chris Troutner baee72c1c6 fix(ipfs-coord): Updating to v7.1.6 2022-03-10 09:27:13 -08:00
Chris Troutner 5c2ab8a8cf Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-10 08:47:54 -08:00
Chris Troutner 11618a796f Merge pull request #80 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.5
2022-03-10 08:35:21 -08:00
Chris Troutner 025ae20704 fix(ipfs-coord): Updating to v7.1.5 2022-03-10 08:33:53 -08:00
Chris Troutner 5f6b98a1e5 Merge pull request #22 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-03-08 09:58:21 -08:00
Chris Troutner e1f5e3fa9c Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-08 09:54:23 -08:00
Chris Troutner fdf73610eb Merge pull request #79 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Updating tests for node.js v16
2022-03-08 09:42:42 -08:00
Chris Troutner b09e77c079 fix(tests): Updating tests for node.js v16 2022-03-08 09:39:54 -08:00
Chris Troutner d583acd6f3 Merge pull request #21 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.4
2022-03-05 14:53:33 -08:00
Chris Troutner 079bd1bebc Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-05 14:48:35 -08:00
Chris Troutner 68ad2561b1 Merge pull request #78 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.4
2022-03-05 14:22:25 -08:00
Chris Troutner 791d11e597 Merge branch 'master' into ct-unstable 2022-03-05 14:20:19 -08:00
Chris Troutner 5489e76f55 fix(ipfs-coord): Updating to v7.1.4 2022-03-05 14:20:13 -08:00
Chris Troutner a205f7967f lint 2022-03-05 09:30:36 -08:00
Chris Troutner 346ba79929 Removing redundent debug log 2022-03-05 09:30:25 -08:00
Chris Troutner 14a9917670 Merge pull request #20 from Permissionless-Software-Foundation/ct-upstream
fix(ipfs-coord): Updating to v7.1.3
2022-03-05 09:21:16 -08:00
Chris Troutner e4fe851871 fix(upstream): Merging with upstream. Updating ipfs-coord 2022-03-05 09:19:35 -08:00
Chris Troutner 496a1dd07c Merge pull request #77 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.3
2022-03-05 08:26:32 -08:00
Chris Troutner a7b8ca9ad5 linting 2022-03-05 08:24:50 -08:00
Chris Troutner 6eb16eb11a fix(ipfs-coord): Updating to v7.1.3 2022-03-05 08:24:36 -08:00
Chris Troutner 589b723629 Increasing sockets to 200 2022-02-24 17:56:41 -08:00
Chris Troutner 5ec45ee9cb fix(ipfs): Reducing sockets from 2000 to 20 2022-02-24 17:35:34 -08:00
Chris Troutner 915d3563c2 Merge pull request #19 from Permissionless-Software-Foundation/ct-unstable
Adding /price/usd REST endpoint
2022-02-02 15:57:05 -08:00
Chris Troutner 9446efe1ae feat(price): Adding /price/usd endpoint 2022-02-02 15:55:31 -08:00
Chris Troutner c573b71a23 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-01-31 19:52:43 -08:00
Chris Troutner 1844f30dc5 Merge pull request #76 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Bumping to v6.8.12
2022-01-31 10:31:20 -08:00
Chris Troutner b6ab2f02f4 Updating package-lock 2022-01-31 10:29:45 -08:00
Chris Troutner b5c76aa5fe fix(ipfs-coord): Bumping to v6.8.12 2022-01-31 10:28:06 -08:00
Chris Troutner 26c0611c51 Merge pull request #17 from Permissionless-Software-Foundation/ct-unstable
Renaming transaction and transactions to txData and txHistory
2022-01-29 09:49:18 -08:00
Chris Troutner 3345160447 fixed unit tests 2022-01-29 09:47:19 -08:00
Chris Troutner 8d4984c4ac fix(txData): Renamed transaction REST API endpoint to txData 2022-01-29 09:31:59 -08:00
Chris Troutner b2cc85df86 feat(txHistory): Renamed transactions endpoint to txHistory 2022-01-29 08:28:13 -08:00
Chris Troutner 51977adad8 fix(production): Adding .env file to supress docker orphan warnings 2022-01-28 20:09:56 -08:00
Chris Troutner 5c7e722808 Merge pull request #16 from Permissionless-Software-Foundation/ct-unstable
Refactored transaction and transactions endpoints
2022-01-28 18:54:43 -08:00
Chris Troutner 03572e9c3d Restoring e2e tests 2022-01-28 15:50:25 -08:00
Chris Troutner 817b40c489 fix(transaction): Refactored transaction 2022-01-28 15:49:54 -08:00
Chris Troutner 744cf60f90 Replacing previous versions of transactions() with new version 2022-01-28 06:25:29 -08:00
Chris Troutner f979cabd50 Merge pull request #15 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-01-20 09:17:03 -08:00
Chris Troutner 30942a4d6b Fixing merge conflicts 2022-01-20 09:14:37 -08:00
Chris Troutner 4101615a4a Merge pull request #75 from Permissionless-Software-Foundation/ct-unstable
Maintenance
2022-01-20 08:05:50 -08:00
Chris Troutner 34a4072384 fix(restart): Removing code the auto-restarts app after 2 hours 2022-01-20 07:56:40 -08:00
Chris Troutner 5fc72be10c fix(fullstack-jwt.js): Got unit test coverage back to 100% 2022-01-20 07:53:12 -08:00
Chris Troutner 7b112a13d4 fix(cors): Fixing bug leading to CORS error 2022-01-12 15:33:04 -08:00
Chris Troutner fe5c573291 Merge pull request #74 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Skipping IPFS stuff in test environment
2022-01-09 11:54:41 -08:00
Chris Troutner 463d7df582 fix(tests): Skipping IPFS stuff in test environment 2022-01-09 11:51:15 -08:00
Chris Troutner 5a1ec640a4 Merge pull request #73 from Permissionless-Software-Foundation/ct-unstable
Adding swarm.key
2022-01-03 11:39:57 -08:00
Chris Troutner 8bb8dbcd23 Updating READMe 2022-01-03 11:35:26 -08:00
Chris Troutner ba212b0997 fix(ipfs): Adding swarm.key file for joining network 2022-01-03 11:22:15 -08:00
41 changed files with 8626 additions and 14609 deletions
+16 -6
View File
@@ -8,13 +8,17 @@ When started, this web server starts an [IPFS](https://ipfs.io) node and connect
## Requirements
- node **^14.17.0**
- npm **^7.13.0**
- node **^14.18.2**
- npm **^8.3.0**
- Docker **^20.10.8**
- Docker Compose **^1.27.4**
## Installation
### Development Environment
**Note:** This software now uses an external go-ipfs IPFS node. The instructions below have not been updated to reflect this.
A development environment will allow you modify the code on-the-fly and contribute to the code base of this repository. [PM2](https://www.npmjs.com/package/pm2) is recommended for running this code base as an IPFS Circuit Relay.
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
@@ -31,17 +35,25 @@ npm install
### Production Environment
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment. However, there is currently [a bug](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38) preventing the Docker container from being used as a Circuit Relay.
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment.
```
docker-compose build --no-cache
docker-compose pull
docker-compose up -d
```
You can bring the containers back up with `docker-compose up -d`.
### Operation Notes
- There is a memory leak in the version of js-ipfs. The app is currently configured to shut down every 8 hours to flush memory. It relies on a process manager like pm2, Docker, or systemd to restart the app after it shuts down, in order to ensure continuous operation.
- The PSF network operates as a private network. It does not connect or interact with the wider PSF network, relying instead on gateways to bridge the two networks, when they need to share content. This improves the performance and experience for everyone in the PSF network. To join the network, you'll need to add the [swarm.key](./swarm.key) file to the IPFS data folder.
- [Instructions on setting up IPFS private networks.](https://github.com/ipfs/go-ipfs/blob/master/docs/experimental-features.md#private-networks)
- For external installations, the swarm.key file will typically go in `~/.ipfs/swarm.key`
- For production Docker containers, the key would go in `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`
## Structure
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](troutsblog.com/blog/clean-architecture).
@@ -83,5 +95,3 @@ Snapshots pinned to IPFS will be listed here.
## License
[MIT](./LICENSE.md)
test
+36 -29
View File
@@ -24,31 +24,37 @@ const config = require('../config') // this first.
const AdminLib = require('../src/adapters/admin')
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
const { wlogger } = require('../src/adapters/wlogger')
const Controllers = require('../src/controllers')
class Server {
constructor () {
// Encapsulate dependencies
this.adminLib = new AdminLib()
this.controllers = new Controllers()
this.mongoose = mongoose
this.config = config
this.process = process
}
async startServer () {
try {
// Create a Koa instance.
const app = new Koa()
app.keys = [config.session]
app.keys = [this.config.session]
// Connect to the Mongo Database.
mongoose.Promise = global.Promise
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
this.mongoose.Promise = global.Promise
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${config.database}`
`Connecting to MongoDB with this connection string: ${this.config.database}`
)
await mongoose.connect(config.database, {
await this.mongoose.connect(this.config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
})
console.log(`Starting environment: ${config.env}`)
console.log(`Debug level: ${config.debugLevel}`)
console.log(`Starting environment: ${this.config.env}`)
console.log(`Debug level: ${this.config.debugLevel}`)
// MIDDLEWARE START
@@ -70,36 +76,37 @@ class Server {
// Enable CORS for testing
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
app.use(cors({ origin: '*' }))
// Attach REST API and JSON RPC controllers to the app.
const Controllers = require('../src/controllers')
const controllers = new Controllers()
await controllers.attachRESTControllers(app)
await this.controllers.attachRESTControllers(app)
app.controllers = controllers
app.controllers = this.controllers
// MIDDLEWARE END
console.log(`Running server in environment: ${config.env}`)
wlogger.info(`Running server in environment: ${config.env}`)
console.log(`Running server in environment: ${this.config.env}`)
wlogger.info(`Running server in environment: ${this.config.env}`)
await app.listen(config.port)
console.log(`Server started on ${config.port}`)
this.server = await app.listen(this.config.port)
console.log(`Server started on ${this.config.port}`)
// Create the system admin user.
const success = await this.adminLib.createSystemUser()
if (success) console.log('System admin user created.')
// Attach the other IPFS controllers
await controllers.attachControllers(app)
// Attach the other IPFS controllers.
// Skip if this is a test environment.
if (this.config.env !== 'test') {
await this.controllers.attachControllers(app)
}
// ipfs-coord has a memory leak. This app shuts down after 4 hours. It
// expects to be run by Docker or pm2, which can automatically restart
// the app.
setTimeout(function () {
process.exit(0)
}, 60000 * 60 * 2) // 2 hours
// Display configuration settings
console.log('\nConfiguration:')
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
return app
} catch (err) {
@@ -108,14 +115,14 @@ class Server {
console.log(
'Exiting after 5 seconds. Depending on process manager to restart.'
)
await sleep(5000)
process.exit(1)
await this.sleep(5000)
this.process.exit(1)
}
}
sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
}
function sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
module.exports = Server
+4 -2
View File
@@ -94,9 +94,11 @@ module.exports = {
: '',
// Settings for production, using external go-ipfs node.
isProduction: process.env.SVC_ENV === 'production' ? true : false,
isProduction: process.env.CONSUMER_ENV === 'production' ? true : false,
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
ipfsApiPort: process.env.IPFS_API_PORT
? parseInt(process.env.IPFS_API_PORT)
: 5001
: 5001,
chatPubSubChan: 'psf-ipfs-chat-001'
}
+7472 -14291
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -23,12 +23,12 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
"dependencies": {
"@psf/bch-js": "5.2.0",
"axios": "0.25.0",
"@psf/bch-js": "6.4.1",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs": "0.60.0",
"ipfs-coord": "6.8.10",
"ipfs": "0.62.3",
"ipfs-coord": "7.1.19",
"ipfs-http-client": "55.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
@@ -45,9 +45,9 @@
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.1",
"line-reader": "0.4.0",
"mongoose": "5.11.15",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.4.17",
"nodemailer": "6.7.5",
"passport-local": "1.0.0",
"public-ip": "4.0.4",
"semver": "^7.3.5",
@@ -55,7 +55,7 @@
"winston-daily-rotate-file": "4.5.0"
},
"devDependencies": {
"apidoc": "0.26.0",
"apidoc": "0.51.1",
"chai": "4.3.0",
"coveralls": "3.1.0",
"eslint": "7.19.0",
@@ -65,10 +65,10 @@
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "4.3.8",
"lodash.clonedeep": "^4.5.0",
"mocha": "8.2.1",
"lodash.clonedeep": "4.5.0",
"mocha": "10.0.0",
"nyc": "15.1.0",
"semantic-release": "17.4.4",
"semantic-release": "19.0.3",
"sinon": "9.2.4",
"standard": "16.0.3",
"uuid": "8.3.2"
+1
View File
@@ -0,0 +1 @@
COMPOSE_PROJECT_NAME=bch-wallet-consumer
+4 -4
View File
@@ -12,7 +12,7 @@ RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
@@ -46,11 +46,11 @@ RUN npm set registry http://94.130.170.209:4873/
# Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer
# Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/ipfs-service-provider
WORKDIR /home/safeuser/ipfs-bch-wallet-consumer
# For development: switch to unstable branch
RUN git checkout ct-unstable
@@ -69,7 +69,7 @@ VOLUME /home/safeuser/keys
EXPOSE 5010
# Start the application.
COPY start-production.sh start-production.sh
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
#CMD ["npm", "start"]
+14 -12
View File
@@ -3,9 +3,9 @@
version: '3.9'
services:
mongo-ipfs-service:
mongo-bch-consumer:
image: mongo
container_name: mongo-ipfs-service
container_name: mongo-bch-consumer
ports:
- '5555:27017' # <host port>:<container port>
volumes:
@@ -13,9 +13,9 @@ services:
command: mongod --logpath=/dev/null # -- quiet
restart: always
ipfs:
image: ipfs/go-ipfs:v0.11.0
container_name: ipfs
ipfs-bch-consumer:
image: ipfs/go-ipfs:v0.12.0
container_name: ipfs-bch-consumer
environment:
MY_ENV_VAR: 'placeholder'
logging:
@@ -30,7 +30,7 @@ services:
- 172.17.0.1:8080:8080
volumes:
- ../data/go-ipfs/data:/data/ipfs
- ../data/go-ipfs/export:/export
- ../../swarm.key:/data/ipfs/swarm.key
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
command:
@@ -42,9 +42,10 @@ services:
]
restart: always
ipfs-service:
build: .
container_name: ipfs-service
bch-consumer:
#build: .
image: christroutner/ipfs-bch-wallet-consumer:v1.8.2
container_name: bch-consumer
logging:
driver: 'json-file'
options:
@@ -52,10 +53,11 @@ services:
max-file: '10'
mem_limit: 500mb
links:
- mongo-ipfs-service
- ipfs
- mongo-bch-consumer
- ipfs-bch-consumer
ports:
- '5010:5010' # <host port>:<container port>
volumes:
- ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
restart: always
+4 -4
View File
@@ -8,7 +8,7 @@
export MNEMONIC="olive two muscle bottom coral ancient wait legend bronze useful process session"
# The human readable name this IPFS node identifies as.
export COORD_NAME=ipfs-service-provider-generic
export COORD_NAME=bch-consumer-generic
# Allow this node to function as a circuit relay. It must not be behind a firewall.
#export ENABLE_CIRCUIT_RELAY=true
@@ -17,19 +17,19 @@ export COORD_NAME=ipfs-service-provider-generic
#export CR_DOMAIN=subdomain.yourdomain.com
# Debug level. 0 = minimal info. 2 = max info.
export DEBUG_LEVEL=2
export DEBUG_LEVEL=1
# END: Optional configuration settings
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
# Configure REST API port
export PORT=5010
# Production settings using external go-ipfs node.
export SVC_ENV=production
export CONSUMER_ENV=production
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
+3 -1
View File
@@ -13,7 +13,7 @@ export COORD_NAME=trout-dev-bch-wallet-consumer
export MNEMONIC="stomach mean catch march census author cousin size south romance pumpkin trap"
# 0 = less verbose. 3 = most verbose
export DEBUG_LEVEL=2
export DEBUG_LEVEL=1
# Production settings that use external IPFS node.
# https://github.com/christroutner/docker-ipfs
@@ -28,4 +28,6 @@ export IPFS_TCP_PORT=4001
# MongoDB connection string.
export DBURL=mongodb://localhost:27017/ipfs-service-dev
export CONSUMER_ENV=production
npm start
+111 -16
View File
@@ -21,15 +21,15 @@ class BchAdapter {
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Connect the RPC handler when the event fires with new data.
this.eventEmitter.on('rpcData', this.rpcHandler)
// this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
@@ -250,8 +250,11 @@ class BchAdapter {
}
}
async getTransactions (address) {
// Get the transaction history for an address.
async getTransactions (address, sortOrder = 'DESCENDING', page = 0) {
try {
console.log('Executing getTransaction() bch adapter')
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -261,8 +264,10 @@ class BchAdapter {
}
const rpcData = {
endpoint: 'transactions',
addresses: [address]
endpoint: 'txHistory',
address: address,
sortOrder,
page
}
// Generate a UUID for the call.
@@ -271,7 +276,7 @@ class BchAdapter {
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
@@ -292,8 +297,13 @@ class BchAdapter {
}
}
async getTransaction (txid) {
// Get details on an array of TXIDs.
async getTransaction (txids) {
try {
console.log(
`Getting txData on these txids: ${JSON.stringify(txids, null, 2)}`
)
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -303,8 +313,8 @@ class BchAdapter {
}
const rpcData = {
endpoint: 'transaction',
txid
endpoint: 'txData',
txids
}
// Generate a UUID for the call.
@@ -313,7 +323,7 @@ class BchAdapter {
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
@@ -325,11 +335,12 @@ class BchAdapter {
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
console.log(`data: ${JSON.stringify(data, null, 2)}`)
return data
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/bch.js/transaction()')
wlogger.error('Error in adapters/bch.js/getTransaction()')
throw err
}
}
@@ -376,6 +387,90 @@ class BchAdapter {
}
}
async utxoIsValid (utxo) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
const rpcData = {
endpoint: 'utxoIsValid',
utxo
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.log('utxoIsValid() error: ', err)
wlogger.error('Error in adapters/bch.js/utxoIsValid()')
throw err
}
}
async getTokenData (tokenId) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
const rpcData = {
endpoint: 'getTokenData',
tokenId
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.log('getTokenData() error: ', err)
wlogger.error('Error in adapters/bch.js/getTokenData()')
throw err
}
}
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
-6
View File
@@ -52,12 +52,6 @@ class FullStackJWT {
// Get's a JWT token from FullStack.cash.
async getJWT () {
try {
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
if (process.env.TEST_TYPE === 'e2e') {
this.apiToken = 'faketoken'
return this.apiToken
}
// Log into the auth server.
await this.jwtLib.register()
+8 -6
View File
@@ -24,12 +24,12 @@ const config = require('../../config')
class Adapters {
constructor (localConfig = {}) {
// Dependency injection.
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Encapsulate dependencies
this.ipfs = new IPFSAdapter(localConfig)
@@ -60,6 +60,8 @@ class Adapters {
// Start the IPFS node.
await this.ipfs.start()
return true
} catch (err) {
console.error('Error in adapters/index.js/start()')
throw err
+12 -8
View File
@@ -2,18 +2,19 @@
top-level IPFS library that combines the individual IPFS-based libraries.
*/
// Local libraries
const IpfsAdapter = require('./ipfs')
const IpfsCoordAdapter = require('./ipfs-coord')
class IPFS {
constructor (localConfig = {}) {
// Dependency injection.
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
// )
// }
// Encapsulate dependencies
this.ipfsAdapter = new IpfsAdapter()
@@ -39,12 +40,15 @@ class IPFS {
// Start ipfs-coord
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
ipfs: this.ipfs,
eventEmitter: this.eventEmitter
ipfs: this.ipfs
// eventEmitter: this.eventEmitter
})
await this.ipfsCoordAdapter.start()
console.log('ipfs-coord is ready.')
// Subscribe to the chat pubsub channel
await this.ipfsCoordAdapter.subscribeToChat()
return true
} catch (err) {
console.error('Error in adapters/ipfs/index.js/start()')
+24 -15
View File
@@ -31,12 +31,12 @@ class IpfsCoordAdapter {
'Instance of IPFS must be passed when instantiating ipfs-coord.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
// )
// }
// Encapsulate dependencies
this.IpfsCoord = IpfsCoord
@@ -121,7 +121,7 @@ class IpfsCoordAdapter {
attachRPCRouter (router) {
try {
_this.ipfsCoord.privateLog = router
_this.ipfsCoord.adapters.orbit.privateLog = router
_this.ipfsCoord.adapters.pubsub.privateLog = router
} catch (err) {
console.error('Error in attachRPCRouter()')
throw err
@@ -291,15 +291,24 @@ class IpfsCoordAdapter {
// This method handles input coming in from other IPFS peers.
// It passes the data on to the REST API library by emitting an event.
peerInputHandler (data) {
try {
// console.log('peerInputHandler triggered with this data: ', data)
// peerInputHandler (data) {
// try {
// // console.log('peerInputHandler triggered with this data: ', data)
//
// this.eventEmitter.emit('rpcData', data)
// } catch (err) {
// console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
// // Do not throw error. This is a top-level function.
// }
// }
this.eventEmitter.emit('rpcData', data)
} catch (err) {
console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
// Do not throw error. This is a top-level function.
}
// Subscribe to the chat pubsub channel
async subscribeToChat () {
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
this.config.chatPubSubChan,
console.log,
this.ipfsCoord.thisNode
)
}
}
+19 -19
View File
@@ -76,7 +76,7 @@ class IpfsAdapter {
const ipfsOptionsExternal = {
host: this.config.ipfsHost,
port: this.config.ipfsApiPort,
agent: http.Agent({ keepAlive: true, maxSockets: 2000 })
agent: http.Agent({ keepAlive: true, maxSockets: 200 })
}
let ipfsOptions = ipfsOptionsEmbedded
@@ -102,9 +102,9 @@ class IpfsAdapter {
console.error('Error in ipfs.js/start()')
// If IPFS crashes because the /blocks directory is full, wipe the directory.
if (err.message.includes('No space left on device')) {
this.rmBlocksDir()
}
// if (err.message.includes('No space left on device')) {
// this.rmBlocksDir()
// }
throw err
}
@@ -120,21 +120,21 @@ class IpfsAdapter {
// 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
}
}
// 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
// }
// }
}
module.exports = IpfsAdapter
+14 -15
View File
@@ -21,15 +21,15 @@ class P2wdbAdapter {
'An instance of IPFS must be passed when instantiating the P2WDB Adapter library.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Connect the RPC handler when the event fires with new data.
this.eventEmitter.on('rpcData', this.rpcHandler)
// this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
@@ -49,12 +49,12 @@ class P2wdbAdapter {
// Convert string input into an object.
// const jsonData = JSON.parse(data)
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
// console.log(`JSON RPC response for ID ${data.payload.id} received.`)
_this.rpcDataQueue.push(data)
} catch (err) {
console.error('Error in p2wdb/rpcHandler(): ', err)
// Do not throw error. This is a top-level function.
// Do not throw error. This is a top-level function.
}
}
@@ -71,7 +71,7 @@ class P2wdbAdapter {
// Add names to the IPFS IDs for each provider.
const initialServiceProviders =
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
const serviceProviders = []
for (let i = 0; i < initialServiceProviders.length; i++) {
const thisProvider = initialServiceProviders[i]
@@ -94,8 +94,7 @@ class P2wdbAdapter {
const outObj = {
serviceProviders,
selectedProvider:
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
selectedProvider: this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
}
// console.log('outObj: ', outObj)
@@ -131,7 +130,7 @@ class P2wdbAdapter {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
if (!selectedProvider) {
throw new Error('No P2WDB Service provider available yet.')
}
@@ -174,7 +173,7 @@ class P2wdbAdapter {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
if (!selectedProvider) {
throw new Error('No P2WDB Service provider available yet.')
}
@@ -253,7 +252,7 @@ class P2wdbAdapter {
cnt++
// Exit if data was returned, or the window for a response expires.
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
+3 -3
View File
@@ -5,7 +5,7 @@
*/
// Public npm libraries.
const EventEmitter = require('events')
// const EventEmitter = require('events')
// Local libraries
const config = require('../../config')
@@ -20,8 +20,8 @@ class Controllers {
this.config = config
// Initialize EventEmitters used to pass event-driven data around the app.
this.eventEmitter = new EventEmitter()
localConfig.eventEmitter = this.eventEmitter
// this.eventEmitter = new EventEmitter()
// localConfig.eventEmitter = this.eventEmitter
// Initialize Clean Architecture classes. Inject dependencies.
this.adapters = new Adapters(localConfig)
+5
View File
@@ -1,5 +1,8 @@
/*
This is the JSON RPC router for the users API
CT 3/5/22: This library can probably be deleted. Handling of the /about endpoint
is now directly controlled by the ipfs-coord library.
*/
// Public npm libraries
@@ -33,6 +36,8 @@ class AboutRPC {
// This is a bit different than other router libraries, because there is
// only one response, which is a string about this node.
async aboutRouter (rpcData) {
console.log('debugging: aboutRouter from ipfs-service-provider triggered')
return {
success: true,
status: 200,
+14 -1
View File
@@ -90,13 +90,14 @@ class JSONRPC {
}
// Process response from REST API.
_this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
// Log the incoming JSON RPC command.
wlogger.info(
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
)
}
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
// Added the property "from" to the parsedData object;
// necessary for calculating rate limits (based on the IPFS ID).
@@ -105,6 +106,16 @@ class JSONRPC {
// Default return string
let retObj = _this.defaultResponse()
// Forward data on to bch adapter if this is the response of a BCH query.
try {
if (parsedData.payload.result.method === 'bch') {
console.log('routing to BCH adapter')
retObj = await _this.adapters.bch.rpcHandler(parsedData)
}
} catch (err) {
/* exit quietly */
}
// Route the command to the appropriate route handler.
switch (parsedData.payload.method) {
case 'users':
@@ -116,6 +127,8 @@ class JSONRPC {
case 'about':
retObj = await _this.aboutController.aboutRouter(parsedData)
break
case 'bch':
retObj = await _this.adapters.bch.rpcHandler(parsedData)
// default:
// // Process response from REST API.
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
+160 -16
View File
@@ -271,10 +271,17 @@ class BchRESTControllerLib {
/**
*
* @api {post} /bch/transactions Transactions
* @apiName Transactions
* @api {post} /bch/txHistory TX History
* @apiName TX History
* @apiGroup REST BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
* It returns the transaction history for an address. This list of TXIDs is
* sorted and paginated.
*
* There are three possible inputs:
* - address: (required) the address to query for a transaction history
* - sortOrder: (optional) will sort results in 'DECENDING' (default) or 'ASCENDING' order.
* - page: (optional) will return a 'page' of 100 results. Default is 0
*
* Given the 'addresses' property returns an array of objects
* with the following properties
@@ -289,7 +296,7 @@ class BchRESTControllerLib {
* Note: For a single address pass the 'addresses' of string type
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/transactions
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/txHistory
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
@@ -317,11 +324,20 @@ class BchRESTControllerLib {
* "error": "Unprocessable Entity"
* }
*/
async transactions (ctx) {
async txHistory (ctx) {
try {
const addr = ctx.request.body.address
// console.log('transactions REST API handler called.')
// console.log(`body: ${JSON.stringify(ctx.request.body, null, 2)}`)
const data = await this.adapters.bch.getTransactions(addr)
const addr = ctx.request.body.address
const sortOrder = ctx.request.body.sortOrder
const page = ctx.request.body.page
const data = await this.adapters.bch.getTransactions(
addr,
sortOrder,
page
)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
@@ -331,11 +347,14 @@ class BchRESTControllerLib {
}
/**
* @api {post} /bch/transaction Transaction
* @apiName Transaction
* @api {post} /bch/txData TX Data
* @apiName txData
* @apiGroup REST BCH
* @apiDescription Get data about a specific transaction.
* Given a transaction id this endpoint will return an object
* @apiDescription Get expanded transaction data for an array of transaction
* IDs. Each call is limited to 20 TXIDs or less.
*
* Get data about specific transactions.
* Given an array of transaction IDs this endpoint will return an array of objects
* with the following properties
*
* - txid: "" - Transaction ID
@@ -353,11 +372,11 @@ class BchRESTControllerLib {
* - isValidSLPTx: - Determines if the transaction was under SLP
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/transaction
* curl -H "Content-Type: application/json" -X POST -d '{ "txids": ["01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"] }' localhost:5001/bch/transaction
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* [{
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
* "version":1,
@@ -406,7 +425,7 @@ class BchRESTControllerLib {
* "time":1568338904,
* "blocktime":1568338904,
* "isValidSLPTx":false
* }
* }]
*
* @apiError UnprocessableEntity Missing required parameters
*
@@ -417,11 +436,19 @@ class BchRESTControllerLib {
* "error": "Unprocessable Entity"
* }
*/
async transaction (ctx) {
async txData (ctx) {
try {
const txid = ctx.request.body.txid
console.log(
`txData called with this body data: ${JSON.stringify(
ctx.request.body,
null,
2
)}`
)
const data = await this.adapters.bch.getTransaction(txid)
const txids = ctx.request.body.txids
const data = await this.adapters.bch.getTransaction(txids)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = data
@@ -462,6 +489,8 @@ class BchRESTControllerLib {
* "pubkey": {
* "success": true,
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
* }
* }
* }
* }
*/
@@ -478,6 +507,121 @@ class BchRESTControllerLib {
}
}
/**
* @api {REST} /bch/utxoIsValid utxoIsValid
* @apiPermission public
* @apiName utxoIsValid
* @apiGroup REST BCH
* @apiDescription Verify if UTXO is valid
* Given a UTXO object (txid and vout), a full node is queried to verify that
* the UTXO still exists in the mempool (true), or if it has been spent (false).
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - success: - Request status
* - isValid: - Boolean: true or false
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxoIsValid", "utxo": {"tx_hash": "17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a", "tx_pos": 0}}}
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "success": true,
* "status": 200,
* "endpoint": "utxoIsValid",
* "isValid": true
* }
* }
*
* }
*/
async utxoIsValid (ctx) {
try {
const utxo = ctx.request.body.utxo
const data = await this.adapters.bch.utxoIsValid(utxo)
// console.log(`utxoIsValid data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {REST} /bch getTokenData
* @apiPermission public
* @apiName getTokenData
* @apiGroup REST BCH
* @apiDescription Get data associated with a token
* Given a token ID, this endpoint will retrieve the IPFS CIDs associated with
* the tokens mutable and immutable data. This is extension of the PS002
* specification for mutable data for tokens:
* https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps002-slp-mutable-data.md
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - success: - Request status
* - getTokenData: - Address public key
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "getTokenData", "tokenId": "c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d" }}
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "success": true,
* "status": 200,
* "endpoint": "getTokenData",
* "tokenData": {
* "genesisData": {
* "type": 1,
* "ticker": 'MT2',
* "name": 'Mutable Token',
* "tokenId": 'c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d',
* "documentUri": 'ipfs://bafybeie7oxpsr7evcnlptecxfdhaqlot4732phukd2ekgvuzoir2frost4',
* "documentHash": '56ed1a5768076a318d02b5db64e125544dca57ab6b2cc7ca61dfa4645d244463',
* "decimals": 0,
* "mintBatonIsActive": true,
* "tokensInCirculationBN": '1000',
* "tokensInCirculationStr": '1000',
* "blockCreated": 739412,
* "totalBurned": '0',
* "totalMinted": '1000'
* },
* "immutableData": 'ipfs://bafybeie7oxpsr7evcnlptecxfdhaqlot4732phukd2ekgvuzoir2frost4',
* "mutableData": 'ipfs://bafybeigotuony53ley3n63hqwyxiqruqn5uamskmci6f645putnc46jju4'
* }
* }
* }
* }
*/
async getTokenData (ctx) {
try {
const tokenId = ctx.request.body.tokenId
const data = await this.adapters.bch.getTokenData(tokenId)
console.log(`getTokenData data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
+16 -6
View File
@@ -56,9 +56,11 @@ class BchRouter {
this.router.post('/balance', this.postBalance)
this.router.post('/utxos', this.postUtxos)
this.router.post('/broadcast', this.postBroadcast)
this.router.post('/transactions', this.postTransactions)
this.router.post('/transaction', this.postTransaction)
this.router.post('/txHistory', this.postTxHistory)
this.router.post('/txData', this.postTxData)
this.router.post('/pubkey', this.postPubKey)
this.router.post('/utxoIsValid', this.utxoIsValid)
this.router.post('/getTokenData', this.getTokenData)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -85,17 +87,25 @@ class BchRouter {
await _this.bchRESTController.broadcast(ctx, next)
}
async postTransactions (ctx, next) {
await _this.bchRESTController.transactions(ctx, next)
async postTxHistory (ctx, next) {
await _this.bchRESTController.txHistory(ctx, next)
}
async postTransaction (ctx, next) {
await _this.bchRESTController.transaction(ctx, next)
async postTxData (ctx, next) {
await _this.bchRESTController.txData(ctx, next)
}
async postPubKey (ctx, next) {
await _this.bchRESTController.pubKey(ctx, next)
}
async utxoIsValid (ctx, next) {
await _this.bchRESTController.utxoIsValid(ctx, next)
}
async getTokenData (ctx, next) {
await _this.bchRESTController.getTokenData(ctx, next)
}
}
module.exports = BchRouter
+5
View File
@@ -14,6 +14,7 @@ const LogsRESTController = require('./logs')
const BchRESTController = require('./bch')
const IpfsRESTController = require('./ipfs')
const P2wdbRESTController = require('./p2wdb')
const PriceRESTController = require('./price')
class RESTControllers {
constructor (localConfig = {}) {
@@ -67,6 +68,10 @@ class RESTControllers {
// Attach the REST API Controllers associated with the /p2wdb route
const p2wdbRESTController = new P2wdbRESTController(dependencies)
p2wdbRESTController.attach(app)
// Attach the REST API Controllers associated with the /price route
const priceRESTController = new PriceRESTController(dependencies)
priceRESTController.attach(app)
}
}
@@ -0,0 +1,83 @@
/*
REST API Controller library for the /bch route
*/
const axios = require('axios')
const { wlogger } = require('../../../adapters/wlogger')
// let _this
class PriceRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /price REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /price REST Controller.'
)
}
// Encapsulate dependencies
this.axios = axios
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
}
/**
* @api {get} /price/usd Get the USD price of BCH
* @apiName Get the USD price of BCH
* @apiGroup Price
* @apiDescription Get the USD price of BCH from Coinbase.
*
*
* @apiExample Example usage:
* curl -X GET "http://localhost:5005/price/usd" -H "accept: application/json"
*
*/
async getUSD (ctx) {
try {
// Request options
const opt = {
method: 'get',
baseURL: 'https://api.coinbase.com/v2/',
url: '/exchange-rates?currency=BCH',
timeout: 15000
}
const response = await this.axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
ctx.body = { usd: Number(response.data.data.rates.USD) }
} catch (err) {
// Write out error to error log.
wlogger.error('Error in GET /price/usd.', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
ctx.throw(err.status, err.message)
} else {
ctx.throw(err.status)
}
} else {
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
}
}
module.exports = PriceRESTControllerLib
+66
View File
@@ -0,0 +1,66 @@
/*
REST API library for /bch route.
*/
// Public npm libraries.
const Router = require('koa-router')
// Local libraries.
const PriceRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
let _this
class PriceRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating Price REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating Price REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.priceRESTController = new PriceRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/price'
this.router = new Router({ prefix: baseUrl })
_this = this
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attaching REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.get('/usd', this.getPrice)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
async getPrice (ctx, next) {
await _this.priceRESTController.getUSD(ctx, next)
}
}
module.exports = PriceRouter
+6 -6
View File
@@ -14,12 +14,12 @@ class UseCases {
'Instance of adapters must be passed in when instantiating Use Cases library.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
// )
// }
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
+3
View File
@@ -0,0 +1,3 @@
/key/swarm/psk/1.0.0/
/base16/
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
+97 -38
View File
@@ -7,7 +7,7 @@
This test will query each REST API endpoint and ensure they are working correctly.
*/
const SERVER = 'http://localhost:5001'
const SERVER = 'http://localhost:5005'
const axios = require('axios')
const assert = require('chai').assert
@@ -23,8 +23,8 @@ describe('#BCH E2E Tests', () => {
const result = await axios.get(url)
// console.log(`data: ${JSON.stringify(result.data, null, 2)}`)
assert.isAbove(result.data.status.state.serviceProviders.length, 0)
assert.isOk(result.data.status.state.selectedServiceProvider)
assert.isAbove(result.data.status.serviceProviders.length, 0)
assert.isOk(result.data.status.selectedProvider)
})
})
@@ -56,9 +56,9 @@ describe('#BCH E2E Tests', () => {
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.address, body.address)
assert.property(result.data, 'bchUtxos')
assert.property(result.data, 'slpUtxos')
assert.equal(result.data[0].address, body.address)
assert.property(result.data[0], 'bchUtxos')
assert.property(result.data[0], 'slpUtxos')
})
})
@@ -74,42 +74,11 @@ describe('#BCH E2E Tests', () => {
assert.equal(result.data.status, 422)
assert.equal(result.data.success, false)
assert.equal(result.data.message, 'TX decode failed')
assert.equal(result.data.message, 'Missing inputs')
assert.equal(result.data.endpoint, 'broadcast')
})
})
describe('#transactions', () => {
it('should get transaction history for an address', async () => {
const body = {
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
}
const url = `${SERVER}/bch/transactions`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.status, 200)
assert.equal(result.data.success, true)
assert.property(result.data, 'transactions')
})
})
describe('#transaction', () => {
it('should get transaction details for a txid', async () => {
const body = {
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
}
const url = `${SERVER}/bch/transaction`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.property(result.data, 'txid')
assert.property(result.data, 'vin')
})
})
describe('#pubkey', () => {
it('should return "not found" if pubkey is not on chain', async () => {
const body = {
@@ -146,4 +115,94 @@ describe('#BCH E2E Tests', () => {
)
})
})
describe('#txHistory', () => {
it('should get transaction history for an address, and sort by default in descending order.', async () => {
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
const body = {
address
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert expected values and properties.
assert.equal(result.data.status, 200)
assert.equal(result.data.success, true)
assert.equal(result.data.address, address)
assert.isArray(result.data.txs)
// Assert expected sorting
assert.isAbove(result.data.txs[0].height, result.data.txs[1].height)
})
it('should sort in ascending order.', async () => {
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
const body = {
address,
sortOrder: 'ASCENDING'
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert expected values and properties.
assert.equal(result.data.status, 200)
assert.equal(result.data.success, true)
assert.equal(result.data.address, address)
assert.isArray(result.data.txs)
// Assert expected sorting
assert.isAbove(result.data.txs[1].height, result.data.txs[0].height)
})
it('should paginate an address with deep tx history', async () => {
const body = {
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert there is no more than 100 entries
assert.isBelow(result.data.txs.length, 101)
})
it('should get the second page of results', async () => {
const body = {
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d',
page: 1
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert there is no more than 100 entries
assert.isBelow(result.data.txs.length, 101)
})
})
describe('#txData', () => {
it('should get transaction details for a txid', async () => {
const body = {
txids: [
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
]
}
const url = `${SERVER}/bch/txData`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.status, 200)
assert.isArray(result.data.txData)
assert.property(result.data.txData[0], 'txid')
assert.property(result.data.txData[0], 'vin')
})
})
})
+53
View File
@@ -0,0 +1,53 @@
/*
Unit tests for the adapters index.js library
*/
// Global npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local libraries
const Adapters = require('../../../src/adapters')
describe('#adapters', () => {
let uut, sandbox
beforeEach(() => {
uut = new Adapters()
sandbox = sinon.createSandbox()
})
afterEach(() => {
sandbox.restore()
})
describe('#start', () => {
it('should start the async adapters', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
sandbox.stub(uut.ipfs, 'start').resolves()
const result = await uut.start()
assert.equal(result, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
uut.config.getJwtAtStartup = false
sandbox.stub(uut.ipfs, 'start').rejects(new Error('test error'))
await uut.start()
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
})
+73 -13
View File
@@ -54,19 +54,19 @@ describe('#bch-use-case', () => {
}
})
it('should throw an error if adapters are not passed in', () => {
try {
const ipfs = {}
uut = new BchAdapter({ ipfs })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
})
// it('should throw an error if adapters are not passed in', () => {
// try {
// const ipfs = {}
// uut = new BchAdapter({ ipfs })
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(
// err.message,
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// })
})
describe('#waitForRPCResponse', () => {
@@ -284,4 +284,64 @@ describe('#bch-use-case', () => {
}
})
})
describe('#utxoIsValid', () => {
it('should validate a utxo', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const utxo = { a: 'b' }
const result = await uut.utxoIsValid(utxo)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.utxoIsValid()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getTokenData', () => {
it('should validate a utxo', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const tokenId = 'blah'
const result = await uut.getTokenData(tokenId)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getTokenData()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
})
+59 -32
View File
@@ -9,7 +9,7 @@ const cloneDeep = require('lodash.clonedeep')
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
const IPFSMock = require('../mocks/ipfs-mock')
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
const EventEmitter = require('events')
const config = require('../../../config')
const mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
describe('#IPFS', () => {
@@ -19,7 +19,7 @@ describe('#IPFS', () => {
beforeEach(() => {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs, eventEmitter: new EventEmitter() })
uut = new IPFSCoordAdapter({ ipfs })
sandbox = sinon.createSandbox()
@@ -47,19 +47,19 @@ describe('#IPFS', () => {
}
})
it('should throw an error if EventEmitter instance is not included', () => {
try {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
)
}
})
// it('should throw an error if EventEmitter instance is not included', () => {
// try {
// const ipfs = IPFSMock.create()
// uut = new IPFSCoordAdapter({ ipfs })
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(
// err.message,
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
// )
// }
// })
})
describe('#start', () => {
@@ -86,6 +86,16 @@ describe('#IPFS', () => {
assert.equal(result, true)
})
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
uut.config.isProduction = true
// Mock dependencies.
uut.IpfsCoord = IPFSCoordMock
const result = await uut.start()
// console.log('result: ', result)
assert.equal(result, true)
config.isProduction = false
})
})
describe('#attachRPCRouter', () => {
@@ -99,8 +109,9 @@ describe('#IPFS', () => {
}
},
adapters: {
orbit: {
privateLog: () => {}
pubsub: {
privateLog: () => {
}
}
}
}
@@ -121,7 +132,7 @@ describe('#IPFS', () => {
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
})
@@ -151,22 +162,38 @@ describe('#IPFS', () => {
})
})
describe('#peerInputHandler', () => {
it('should emit an event trigger', () => {
const data = 'some data'
// describe('#peerInputHandler', () => {
// it('should emit an event trigger', () => {
// const data = 'some data'
//
// uut.peerInputHandler(data)
//
// assert.isOk(true, 'Not throwing an error is a success')
// })
//
// it('should catch and report errors', () => {
// // Force an error
// sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
//
// uut.peerInputHandler()
//
// assert.isOk(true, 'Not throwing an error is a success.')
// })
// })
uut.peerInputHandler(data)
describe('#subscribeToChat', () => {
it('should subscribe to the chat channel', async () => {
// Mock dependencies
uut.ipfsCoord = {
adapters: {
pubsub: {
subscribeToPubsubChannel: async () => {
}
}
}
}
assert.isOk(true, 'Not throwing an error is a success')
})
it('should catch and report errors', () => {
// Force an error
sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
uut.peerInputHandler()
assert.isOk(true, 'Not throwing an error is a success.')
await uut.subscribeToChat()
})
})
})
+57 -23
View File
@@ -4,10 +4,11 @@
const assert = require('chai').assert
const sinon = require('sinon')
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
const IPFSMock = require('../mocks/ipfs-mock')
const config = require('../../../config')
// config.isProduction = true;
describe('#IPFS-adapter', () => {
let uut
let sandbox
@@ -18,7 +19,27 @@ describe('#IPFS-adapter', () => {
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
afterEach(() => {
sandbox.restore()
})
describe('#constructor', () => {
it('should instantiate IPFS Lib in dev mode.', async () => {
const _uut = new IPFSLib()
assert.exists(_uut)
assert.isFunction(_uut.start)
assert.isFunction(_uut.stop)
})
it('should instantiate dev IPFS Lib in production mode.', async () => {
config.isProduction = true
const _uut = new IPFSLib()
assert.exists(_uut)
assert.isFunction(_uut.start)
assert.isFunction(_uut.stop)
config.isProduction = false
})
})
describe('#start', () => {
it('should return a promise that resolves into an instance of IPFS.', async () => {
@@ -33,6 +54,18 @@ describe('#IPFS-adapter', () => {
assert.property(result, 'config')
})
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
// Mock dependencies.
uut.IPFS = IPFSMock
uut.config.isProduction = true
const result = await uut.start()
// console.log('result: ', result)
assert.equal(uut.isReady, true)
assert.property(result, 'config')
})
it('should catch and throw an error', async () => {
try {
// Force an error
@@ -52,7 +85,8 @@ describe('#IPFS-adapter', () => {
it('should stop the IPFS node', async () => {
// Mock dependencies
uut.ipfs = {
stop: () => {}
stop: () => {
}
}
const result = await uut.stop()
@@ -61,24 +95,24 @@ 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('#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')
// }
// })
// })
})
@@ -333,7 +333,7 @@ describe('#UserRPC', () => {
assert.equal(result.endpoint, 'updateUser')
assert.equal(result.success, false)
assert.equal(result.status, 422)
assert.include(result.message, 'Cannot read property')
assert.include(result.message, 'Cannot read')
})
})
@@ -375,7 +375,7 @@ describe('#UserRPC', () => {
assert.equal(result.endpoint, 'getUser')
assert.equal(result.success, false)
assert.equal(result.status, 422)
assert.include(result.message, 'Cannot read property')
assert.include(result.message, 'Cannot read')
})
})
@@ -213,7 +213,7 @@ describe('#BCH-REST-Controller', () => {
})
})
describe('#transactions', () => {
describe('#txHistory', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
@@ -225,7 +225,7 @@ describe('#BCH-REST-Controller', () => {
address: 'blah'
}
await uut.transactions(ctx)
await uut.txHistory(ctx)
assert.fail('Unexpected result')
} catch (err) {
@@ -242,14 +242,14 @@ describe('#BCH-REST-Controller', () => {
address: 'blah'
}
await uut.transactions(ctx)
await uut.txHistory(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#transaction', () => {
describe('#txData', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
@@ -261,7 +261,7 @@ describe('#BCH-REST-Controller', () => {
txid: 'blah'
}
await uut.transaction(ctx)
await uut.txData(ctx)
assert.fail('Unexpected result')
} catch (err) {
@@ -280,7 +280,7 @@ describe('#BCH-REST-Controller', () => {
txid: 'blah'
}
await uut.transaction(ctx)
await uut.txData(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
@@ -322,4 +322,76 @@ describe('#BCH-REST-Controller', () => {
assert.equal(ctx.status, 200)
})
})
describe('#utxoIsValid', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'utxoIsValid')
.rejects(new Error('test error'))
ctx.request.body = {
utxo: 'blah'
}
await uut.utxoIsValid(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'utxoIsValid').resolves({ status: 200 })
ctx.request.body = {
utxo: 'blah'
}
await uut.utxoIsValid(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#getTokenData', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getTokenData')
.rejects(new Error('test error'))
ctx.request.body = {
tokenId: 'blah'
}
await uut.getTokenData(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'getTokenData').resolves({ status: 200 })
ctx.request.body = {
tokenId: 'blah'
}
await uut.getTokenData(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
})
@@ -14,7 +14,8 @@ let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('Contact', () => {
before(async () => {})
before(async () => {
})
beforeEach(() => {
uut = new ContactController()
@@ -36,7 +37,7 @@ describe('Contact', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -14,7 +14,8 @@ let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('Logapi', () => {
before(async () => {})
before(async () => {
})
beforeEach(() => {
uut = new LogsApiController()
@@ -35,7 +36,7 @@ describe('Logapi', () => {
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -69,7 +69,7 @@ describe('#Users-REST-Controller', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -91,9 +91,9 @@ describe('#Users-REST-Controller', () => {
assert.property(ctx.response.body, 'user')
assert.property(ctx.response.body, 'token')
// Used by downstream tests.
// testUser = ctx.response.body.user
// console.log('testUser: ', testUser)
// Used by downstream tests.
// testUser = ctx.response.body.user
// console.log('testUser: ', testUser)
})
})
@@ -181,7 +181,7 @@ describe('#Users-REST-Controller', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -223,7 +223,7 @@ describe('#Users-REST-Controller', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
+63
View File
@@ -0,0 +1,63 @@
/*
Unit tests for the bin/server.js file
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local libraries
const Server = require('../../../bin/server')
describe('#server', () => {
let uut, sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new Server()
})
afterEach(() => sandbox.restore())
describe('#startServer', () => {
it('should start the server', async () => {
// Mock dependencies
sandbox.stub(uut.mongoose, 'connect').resolves()
sandbox.stub(uut.controllers, 'attachRESTControllers').resolves()
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
sandbox.stub(uut.controllers, 'attachControllers').resolves()
uut.config.env = 'dev'
const result = await uut.startServer()
// console.log('result: ', result)
assert.property(result, 'env')
// Turn off the server.
uut.server.close()
// Restor config env
uut.config.env = 'test'
})
it('should exit on failure', async () => {
// Force an error
sandbox.stub(uut.mongoose, 'connect').rejects(new Error('test error'))
// Prevent default behavior of exiting the program.
sandbox.stub(uut, 'sleep').resolves()
sandbox.stub(uut.process, 'exit').returns()
await uut.startServer()
// Not throwing an error is a success
})
})
describe('#sleep', () => {
it('should execute', async () => {
await uut.sleep(1)
})
})
})
+8
View File
@@ -93,6 +93,14 @@ class BchUseCaseMock {
return {}
}
async utxoIsValid() {
return {}
}
async getTokenData() {
return {}
}
async waitForRPCResponse () {
return {}
}
+2
View File
@@ -8,6 +8,8 @@ class IPFSCoord {
}
async start () {}
async subscribeToChat() {}
}
module.exports = IPFSCoord
+9 -9
View File
@@ -153,7 +153,7 @@ describe('#users-use-case', () => {
await uut.getAllUsers()
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
// assert.isArray(users)
// assert.isArray(users)
})
it('should catch and throw an error', async () => {
@@ -228,8 +228,8 @@ describe('#users-use-case', () => {
// Assert that the expected properties for the user model exist.
// assert.property(result, 'type')
assert.property(result, '_id')
// assert.property(result, 'email')
// assert.property(result, 'name')
// assert.property(result, 'email')
// assert.property(result, 'name')
})
})
@@ -241,7 +241,7 @@ describe('#users-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -346,7 +346,7 @@ describe('#users-use-case', () => {
// assert.equal(result.name, 'testy tester')
// })
// TODO: verify that an admin can change the type of a user
// TODO: verify that an admin can change the type of a user
})
describe('#authUser', () => {
@@ -356,9 +356,9 @@ describe('#users-use-case', () => {
await uut.authUser('test@test.com', 'password')
// console.log('user: ', user)
// assert.property(user, '_id')
// assert.property(user, 'email')
// assert.property(user, 'name')
// assert.property(user, '_id')
// assert.property(user, 'email')
// assert.property(user, 'name')
})
it('should throw an error if no user matches the login', async () => {
@@ -399,7 +399,7 @@ describe('#users-use-case', () => {
assert.fail('Unexpected code path.')
} catch (err) {
// console.log(err)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})