Compare commits

...
47 Commits
Author SHA1 Message Date
Chris Troutner d734a754d6 Merge pull request #8 from Permissionless-Software-Foundation/ct-unstable
syncing upstream ipfs-service-provider
2021-12-24 17:25:18 -08:00
Chris Troutner d4863e7d7d Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2021-12-24 17:16:47 -08:00
Chris Troutner e6941f7f7f Merge pull request #61 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-cood): Updating to v6.7.6
2021-12-24 17:10:49 -08:00
Chris Troutner 4fdf35a55f fix(ipfs-cood): Updating to v6.7.6 2021-12-24 17:07:54 -08:00
Chris Troutner bb44b75973 Merge pull request #7 from Permissionless-Software-Foundation/ct-unstable
syncing with upstream ipfs-service-provider
2021-12-24 15:39:31 -08:00
Chris Troutner 698cca555b Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer into ct-unstable 2021-12-24 15:36:16 -08:00
Chris Troutner 76b3740e74 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2021-12-24 15:35:28 -08:00
Chris Troutner 2622038df5 Updating api docs 2021-12-24 15:35:12 -08:00
Chris Troutner f1fbe34b58 Merge pull request #60 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v6.7.5
2021-12-24 15:26:04 -08:00
Chris Troutner d232b6404d fix(ipfs-coord): Updating to v6.7.5 2021-12-24 15:24:38 -08:00
Chris Troutner 9f92cef122 feat(p2wdb): Added endpoint for writing to P2WDB 2021-12-22 16:49:17 -08:00
Chris Troutner 18e98f9a3b feat(p2wdb): Added endpoint to select different P2WDB provider 2021-12-22 11:02:41 -08:00
Chris Troutner 938cc755f1 feat(p2wdb): Added REST API endpoints for P2WDB 2021-12-21 19:17:41 -08:00
Chris Troutner 6db6db8187 fix(ipfs-coord): Refactoring. Adding pollForP2wdbServices() 2021-12-21 18:20:55 -08:00
Chris Troutner 35af9c08fa Adding minor comments 2021-12-21 17:37:39 -08:00
Chris Troutner 2caf18977e Merge pull request #6 from Permissionless-Software-Foundation/ct-unstable
fix(bch/getStats()): Adding names to service provider IDs
2021-12-17 15:13:48 -08:00
Chris Troutner 8857242570 fix(bch/getStats()): Adding names to service provider IDs 2021-12-14 17:04:55 -08:00
Chris Troutner 4f0e59ea61 Merge pull request #5 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2021-12-06 12:57:40 -08:00
Chris Troutner a1a910c3b5 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2021-12-06 12:56:32 -08:00
Chris Troutner 1bf8f756e5 Merge pull request #59 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Deleting /blocks dir if it prevents app from starting
2021-12-06 12:48:48 -08:00
Chris Troutner 1e513cc133 fix(ipfs): Deleting /blocks dir if it prevents app from starting 2021-12-06 12:46:22 -08:00
Chris Troutner 2f48f1bb61 Merge pull request #4 from Permissionless-Software-Foundation/ct-unstable
feat(REST API): Adding and upgrading REST API endpoints
2021-12-06 12:17:04 -08:00
Chris Troutner 70c81af88e Added REST endpoint for ipfs-relays 2021-12-05 12:00:24 -08:00
Chris Troutner cdbb8b18b6 Added REST API endpoint for ipfs/peers 2021-12-04 10:08:30 -08:00
Chris Troutner b3c3bb4ae2 Added ipfs-status endpoint 2021-12-04 08:57:44 -08:00
Chris Troutner 4d291f7310 Refining BCH REST API endpoints 2021-12-04 07:52:22 -08:00
Chris Troutner 8641a4100c Updating gitignore 2021-12-02 14:09:43 -08:00
Chris Troutner 3a30dd6b03 fix(mongo): Changing db name 2021-12-02 14:08:33 -08:00
Chris Troutner 762ed8dd43 Merge branch 'ct-unstable' 2021-12-02 10:25:09 -08:00
Chris Troutner 17914f2937 Removing default service-provider in config file 2021-12-02 10:24:57 -08:00
Chris Troutner aa5b921dee Merge pull request #3 from Permissionless-Software-Foundation/ct-unstable
Added REST API endpoints for all endpoints offered by service-provider
2021-12-02 10:14:20 -08:00
Chris Troutner 717744094e feat(pubKey): Added pubkey endpoint 2021-12-02 10:09:41 -08:00
Chris Troutner 123754f6ea Added transaction endpoint 2021-12-02 08:07:17 -08:00
Chris Troutner 766826d86e fix(bch): Moved bch use-cases to adapters 2021-11-30 18:18:56 -08:00
Chris Troutner 74c6bc5737 Merge pull request #58 from Permissionless-Software-Foundation/ct-unstable
fix(garbage collection): Updating IPFS config for more conservative GC
2021-11-27 15:14:30 -08:00
Chris Troutner bac6c8b1e9 Disabling debug code 2021-11-27 15:13:23 -08:00
Chris Troutner 9e7d955bc4 fix(garbage collection): Updating IPFS config for more conservative GC 2021-11-27 15:12:08 -08:00
Chris Troutner fc3a2fe57f Merge pull request #2 from Permissionless-Software-Foundation/ct-unstable
feat(BCH): Added BCH REST API for proxying to IPFS wallet service
2021-11-26 15:31:24 -08:00
Chris Troutner 14150f81e9 feat(BCH): Added BCH REST API for proxying to IPFS wallet service 2021-11-26 15:28:41 -08:00
Chris Troutner 52ca71ec16 Got existing unit tests passing 2021-11-26 13:17:16 -08:00
Chris Troutner ab5aa94353 Got balance REST API to bridge to IPFS service 2021-11-26 12:31:38 -08:00
Chris Troutner 453918a606 Displaying service provider in status API 2021-11-26 08:20:52 -08:00
Chris Troutner e73afdcd6c Wired /bch REST API to BCH use-case lib 2021-11-26 07:23:00 -08:00
Chris Troutner 43759b7a3e Added REST API route to get BCH system status 2021-11-26 07:09:37 -08:00
Chris Troutner d25545ae83 Successfully connected to wallet service 2021-11-26 06:02:35 -08:00
Chris Troutner 23706b6198 Merge pull request #1 from Permissionless-Software-Foundation/ct-unstable
fix(CI/CD): Testing CI/CD pipeline
2021-11-23 10:22:20 -08:00
Chris Troutner 35f09cc4bd fix(CI/CD): Testing CI/CD pipeline 2021-11-23 10:20:17 -08:00
41 changed files with 3517 additions and 36 deletions
+2
View File
@@ -7,6 +7,7 @@
#logs
logs/*.json
*.log
*.log.*
npm-debug.log*
# Runtime data
@@ -65,5 +66,6 @@ system-user-*.json
orbitdb
ipfsdata
.ipfsdata
run-dev.sh
!README.md
+2
View File
@@ -83,3 +83,5 @@ Snapshots pinned to IPFS will be listed here.
## License
[MIT](./LICENSE.md)
test
+14 -1
View File
@@ -78,5 +78,18 @@ module.exports = {
// BCH Mnemonic for generating encryption keys and payment address
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
// Preferred provider running ipfs-bch-wallet-service for BCH wallet.
preferredProvider: process.env.PREFERRED_PROVIDER
? process.env.PREFERRED_PROVIDER
: '',
// : 'QmdBGB8S6sEr19gaAxJYjhRbz5ZmMiH5a1JyyMmaxcRKnY'
// : 'QmUTx6KqYKVZbKpKxR7vGDUgZFYVvVVyEWDeCYq4GwBCff'
// Used for development
// Preferred P2WDB provider
preferredP2wdbProvider: process.env.PREFERRED_P2WDB_PROVIDER
? process.env.PREFERRED_P2WDB_PROVIDER
: ''
}
+1 -1
View File
@@ -7,6 +7,6 @@
module.exports = {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/ipfs-service-dev',
database: 'mongodb://localhost:27017/ipfs-bch-consumer-dev',
env: 'dev'
}
+1 -1
View File
@@ -13,6 +13,6 @@ module.exports = {
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
database: process.env.DBURL
? process.env.DBURL
: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
: 'mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod',
env: 'prod'
}
+1 -1
View File
@@ -7,6 +7,6 @@
module.exports = {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/ipfs-service-test',
database: 'mongodb://localhost:27017/ipfs-bch-consumer-test',
env: 'test'
}
+1 -1
View File
@@ -1,6 +1,6 @@
const common = require('./env/common')
const env = process.env.SVC_ENV || 'development'
const env = process.env.CONSUMER_ENV || 'development'
const config = require(`./env/${env}`)
module.exports = Object.assign({}, common, config)
+9
View File
@@ -0,0 +1,9 @@
# Developer Documentation
This directory contains documentation for developers.
## Theory of Operation
This app provides a bridge between the Web 2.0 REST API world and the Web3.0 IPFS world. Web apps (like wallet.fullstack.cash) can interact with the REST API provided by the this web server app. This app then pulls the back-end infrastructure through a the IPFS network.
When started, this app will spin up an IPFS node and connect to the PSF network using [ipfs-coord](https://www.npmjs.com/package/ipfs-coord). It will find and connect with a BCH wallet service provider via IPFS. Clients connecting to the REST API can then access BCH wallet infrastructure via the REST API.
+11 -9
View File
@@ -14,7 +14,7 @@
"axios": "0.21.1",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs-coord": "6.7.4",
"ipfs-coord": "6.7.6",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -35,6 +35,7 @@
"nodemailer": "6.4.17",
"passport-local": "1.0.0",
"public-ip": "4.0.4",
"semver": "^7.3.5",
"winston": "3.3.3",
"winston-daily-rotate-file": "4.5.0"
},
@@ -9610,9 +9611,9 @@
}
},
"node_modules/ipfs-coord": {
"version": "6.7.4",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.4.tgz",
"integrity": "sha512-nboEp4xge4ZE8VWSCPhIB5PXQafKChacLOTVwxFfmjzgrXenacw3PBuR/UGxMJ3JX+t13ee/M662Jd8nzp7rZA==",
"version": "6.7.6",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.6.tgz",
"integrity": "sha512-Uif7Th0+jMb6E7/LhALgpuVFHNV2jlRWzOW8T/u9AuF83CyiHFZJIgJY06pd/TH6r7wH2VUahrrcCOlxgiQJQg==",
"license": "MIT",
"dependencies": {
"axios": "0.21.4",
@@ -22413,8 +22414,9 @@
},
"node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"resolved": "http://94.130.170.209:4873/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"license": "ISC",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -33137,9 +33139,9 @@
}
},
"ipfs-coord": {
"version": "6.7.4",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.4.tgz",
"integrity": "sha512-nboEp4xge4ZE8VWSCPhIB5PXQafKChacLOTVwxFfmjzgrXenacw3PBuR/UGxMJ3JX+t13ee/M662Jd8nzp7rZA==",
"version": "6.7.6",
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.6.tgz",
"integrity": "sha512-Uif7Th0+jMb6E7/LhALgpuVFHNV2jlRWzOW8T/u9AuF83CyiHFZJIgJY06pd/TH6r7wH2VUahrrcCOlxgiQJQg==",
"requires": {
"axios": "0.21.4",
"bch-encrypt-lib": "2.0.0",
@@ -43102,7 +43104,7 @@
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"resolved": "http://94.130.170.209:4873/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"requires": {
"lru-cache": "^6.0.0"
+7 -6
View File
@@ -6,14 +6,14 @@
"scripts": {
"start": "node index.js",
"test": "npm run test:all",
"test:all": "export SVC_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export SVC_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"test:all": "export CONSUMER_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:temp": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"lint": "standard --env mocha --fix",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export SVC_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
"coverage:report": "export CONSUMER_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
},
"author": "Chris Troutner <chris.troutner@gmail.com>",
"license": "MIT",
@@ -28,7 +28,7 @@
"axios": "0.21.1",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs-coord": "6.7.4",
"ipfs-coord": "6.7.6",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -49,6 +49,7 @@
"nodemailer": "6.4.17",
"passport-local": "1.0.0",
"public-ip": "4.0.4",
"semver": "^7.3.5",
"winston": "3.3.3",
"winston-daily-rotate-file": "4.5.0"
},
+430
View File
@@ -0,0 +1,430 @@
/*
This library contains business-logic for dealing with BCH wallet service
providers. Most of these functions are called by the /bch REST API endpoints.
*/
// Public npm libraries
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
// Local libraries
const { wlogger } = require('../wlogger')
let _this
class BchAdapter {
constructor (localConfig = {}) {
// console.log('BCH localConfig: ', localConfig)
this.ipfs = localConfig.ipfs
if (!this.ipfs) {
throw new Error(
'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.'
)
}
// Connect the RPC handler when the event fires with new data.
this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
this.jsonrpc = jsonrpc
// A queue for holding RPC data that has arrived.
this.rpcDataQueue = []
_this = this // Global handle on instance of this Class.
}
// This handler is triggered when RPC data comes in over IPFS.
// Handle RPC input, and add the response to the RPC queue.
// Once in the queue, it will get processed by waitForRPCResponse()
rpcHandler (data) {
try {
// Convert string input into an object.
// const jsonData = JSON.parse(data)
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
_this.rpcDataQueue.push(data)
} catch (err) {
console.error('Error in rest-api.js/rpcHandler(): ', err)
// Do not throw error. This is a top-level function.
}
}
// Get the status of BCH wallet services this node can talk to. Returns an
// array of BCH wallet service providers.
async getStatus () {
try {
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
// const status = {
// state: this.ipfs.ipfsCoordAdapter.state
// }
// Add names to the IPFS IDs for each provider.
const initialServiceProviders =
this.ipfs.ipfsCoordAdapter.state.serviceProviders
const serviceProviders = []
for (let i = 0; i < initialServiceProviders.length; i++) {
const thisProvider = initialServiceProviders[i]
const providerData = peerData.filter((x) => x.from === thisProvider)
if (providerData.length) {
const provObj = {
ipfsId: thisProvider,
name: providerData[0].data.jsonLd.name
}
serviceProviders.push(provObj)
}
}
// console.log(
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
// )
const outObj = {
serviceProviders,
selectedProvider:
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
}
// console.log('outObj: ', outObj)
return outObj
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getStatus()')
throw err
}
}
// Choose the BCH wallet service to use.
async selectProvider (providerId) {
try {
this.ipfs.ipfsCoordAdapter.config.preferredProvider = providerId
return true
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getStatus()')
throw err
}
}
// Get the BCH balance for an array of addresses.
async getBalances (addrs) {
try {
// console.log('addrs: ', addrs)
// 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: 'balance',
addresses: addrs
}
// 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('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getBalances()')
throw err
}
}
async getUtxos (addr) {
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: 'utxos',
address: addr
}
// 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[0]
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getUtxos()')
throw err
}
}
async broadcast (hex) {
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: 'broadcast',
hex
}
// 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('createUser() error: ', err)
wlogger.error('Error in adapters/bch.js/broadcast()')
throw err
}
}
async getTransactions (address) {
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: 'transactions',
addresses: [address]
}
// 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('createUser() error: ', err)
wlogger.error('Error in adapters/bch.js/transactions()')
throw err
}
}
async getTransaction (txid) {
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: 'transaction',
txid
}
// 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('createUser() error: ', err)
wlogger.error('Error in adapters/bch.js/transaction()')
throw err
}
}
async getPubKey (address) {
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: 'pubkey',
address
}
// 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('getPubKey() error: ', err)
wlogger.error('Error in adapters/bch.js/getPubKey()')
throw err
}
}
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
// Initialize variables for tracking the return data.
let dataFound = false
let cnt = 0
// Default return value, if the remote computer does not respond in time.
let data = {
success: false,
message: 'request timed out',
data: ''
}
// Loop that waits for a response from the service provider.
do {
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
for (let i = 0; i < this.rpcDataQueue.length; i++) {
const rawData = this.rpcDataQueue[i]
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
if (rawData.payload.id === rpcId) {
dataFound = true
// console.log('data was found in the queue')
data = rawData.payload.result.value
// Remove the data from the queue
this.rpcDataQueue.splice(i, 1)
break
}
}
// Wait between loops.
// await this.sleep(1000)
await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
cnt++
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
return data
} catch (err) {
console.error('Error in waitForRPCResponse()')
throw err
}
}
}
module.exports = BchAdapter
+14 -1
View File
@@ -16,13 +16,24 @@ const Nodemailer = require('./nodemailer')
// const { wlogger } = require('./wlogger')
const JSONFiles = require('./json-files')
const FullStackJWT = require('./fullstack-jwt')
const BCH = require('./bch')
const P2WDB = require('./p2wdb')
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.'
)
}
// Encapsulate dependencies
this.ipfs = new IPFSAdapter()
this.ipfs = new IPFSAdapter(localConfig)
localConfig.ipfs = this.ipfs
this.localdb = new LocalDB()
this.logapi = new LogsAPI()
this.passport = new Passport()
@@ -30,6 +41,8 @@ class Adapters {
this.jsonFiles = new JSONFiles()
this.bchjs = new BCHJS()
this.config = config
this.bch = new BCH(localConfig)
this.p2wdb = new P2WDB(localConfig)
// Get a valid JWT API key and instance bch-js.
this.fullStackJwt = new FullStackJWT(config)
+134 -1
View File
@@ -7,6 +7,14 @@ 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.'
)
}
// Encapsulate dependencies
this.ipfsAdapter = new IpfsAdapter()
this.IpfsCoordAdapter = IpfsCoordAdapter
@@ -31,7 +39,8 @@ class IPFS {
// Start ipfs-coord
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
ipfs: this.ipfs
ipfs: this.ipfs,
eventEmitter: this.eventEmitter
})
await this.ipfsCoordAdapter.start()
console.log('ipfs-coord is ready.')
@@ -49,6 +58,130 @@ class IPFS {
throw err
}
}
// Get the status of this IPFS node.
getStatus () {
try {
// console.log(
// 'this.ipfsCoordAdapter.ipfsCoord.thisNode: ',
// this.ipfsCoordAdapter.ipfsCoord.thisNode
// )
const statusObj = {
ipfsId: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsId,
multiAddrs: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsMultiaddrs,
bchAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.bchAddr,
slpAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.slpAddr,
pubKey: this.ipfsCoordAdapter.ipfsCoord.thisNode.pubKey,
peers: this.ipfsCoordAdapter.ipfsCoord.thisNode.peerList.length,
relays: this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData.length
}
return statusObj
} catch (err) {
console.error('Error in ipfs-coord.js/getStatus()')
throw err
}
}
// Get details on the other peers this node is connected to.
async getPeers (showAll) {
try {
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
let ipfsPeers =
await this.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.getPeers()
// console.log('ipfsPeers: ', ipfsPeers)
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
// console.log('filtered ipfsPeers: ', ipfsPeers)
// Loop through each IPFS peer and hydrate it with data from the peerData.
for (let i = 0; i < ipfsPeers.length; i++) {
const thisPeer = ipfsPeers[i]
if (!showAll) {
// Delete properties that don't contain good info.
delete thisPeer.muxer
delete thisPeer.latency
delete thisPeer.streams
}
// Get the ipfs-coord peer data for this peer.
let thisPeerData = peerData.filter((x) =>
x.from.includes(thisPeer.peer)
)
thisPeerData = thisPeerData[0]
// Skip if peerData for this IPFS peer could not be found.
if (!thisPeerData) continue
try {
// console.log('thisPeerData: ', thisPeerData)
// Add data to the IPFS peer data.
thisPeer.name = thisPeerData.data.jsonLd.name
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
thisPeer.version = thisPeerData.data.jsonLd.version
if (showAll) {
// Add all the peer data.
thisPeer.peerData = thisPeerData
}
} catch (err) {
console.log(
`Error trying to hydrate peer ${thisPeer.peer}: ${err.message}`
)
}
}
return ipfsPeers
} catch (err) {
console.error('Error in getPeers(): ', err)
throw err
}
}
// Get data about the known Circuit Relays. Hydrate with data from peers list.
getRelays () {
try {
const relayData = this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`relayData: ${JSON.stringify(relayData, null, 2)}`)
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
for (let i = 0; i < relayData.length; i++) {
const thisRelay = relayData[i]
// Find the peer that corresponds to this relay.
const thisPeer = peerData.filter((x) =>
x.from.includes(thisRelay.ipfsId)
)
// console.log('thisPeer: ', thisPeer)
// If the peer couldn't be found, skip.
if (!thisPeer.length) {
thisRelay.name = ''
continue
}
thisRelay.name = thisPeer[0].data.jsonLd.name
}
return relayData
} catch (err) {
console.error('Error in getRelays(): ', err)
throw err
}
}
// Expects an array of peers and returns an array of peers with duplicates
// removed.
_removeDuplicatePeers (arr) {
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
return arr.filter((v, i, a) => a.findIndex((t) => t.peer === v.peer) === i)
}
}
module.exports = IPFS
+187 -1
View File
@@ -8,11 +8,18 @@
const IpfsCoord = require('ipfs-coord')
const BCHJS = require('@psf/bch-js')
const publicIp = require('public-ip')
const semver = require('semver')
// Local libraries
const config = require('../../../config')
// const JSONRPC = require('../../controllers/json-rpc/')
// The minimum version of ipfs-bch-wallet-service that this wallet can work with.
const MIN_BCH_WALLET_VERSION = '1.11.11'
const WALLET_PROTOCOL = 'bch-wallet'
const MIN_P2WDB_VERSION = '1.4.0'
const P2WDB_PROTOCOL = 'p2wdb'
let _this
class IpfsCoordAdapter {
@@ -24,6 +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.'
)
}
// Encapsulate dependencies
this.IpfsCoord = IpfsCoord
@@ -31,10 +44,23 @@ class IpfsCoordAdapter {
this.bchjs = new BCHJS()
this.config = config
this.publicIp = publicIp
this.semver = semver
// Properties of this class instance.
this.isReady = false
// Periodically poll services for available wallet service providers.
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
this.pollP2wdbServiceInterval = setInterval(this.pollForP2wdbServices, 11000)
// State object. TODO: Make this more robust.
this.state = {
serviceProviders: [],
selectedServiceProvider: '',
p2wdbProviders: [],
selectedP2wdbProvider: ''
}
_this = this
}
@@ -62,7 +88,7 @@ class IpfsCoordAdapter {
type: 'node.js',
// type: 'browser',
bchjs: this.bchjs,
privateLog: console.log, // Default to console.log
privateLog: this.peerInputHandler,
isCircuitRelay: this.config.isCircuitRelay,
circuitRelayInfo,
apiInfo: this.config.apiInfo,
@@ -90,6 +116,166 @@ class IpfsCoordAdapter {
throw err
}
}
// Poll the ipfs-coord coordination channel for available P2WDB service
// providers. This method is called periodically by a timer-interval.
pollForP2wdbServices () {
try {
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
const peers = _this.ipfsCoord.thisNode.peerList
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
// Array of objects. Each object is the IPFS ID of the peer and contains
// data about that peer.
const peerData = _this.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
for (let i = 0; i < peers.length; i++) {
const thisPeer = peers[i]
const thisData = peerData.filter((x) => x.from === thisPeer)
const thisPeerData = thisData[0]
// Create a 'fingerprint' that defines the wallet service.
const protocol = thisPeerData.data.jsonLd.protocol
const version = thisPeerData.data.jsonLd.version
// console.log(
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`
// )
let versionMatches = false
if (version) {
versionMatches = _this.semver.gt(version, MIN_P2WDB_VERSION)
}
// p2wdbProviders: [],
// selectedP2wdbProvider: ''
// Ignore any peers that don't match the fingerprint for a BCH wallet
// service.
if (protocol && protocol.includes(P2WDB_PROTOCOL) && versionMatches) {
// console.log('Matching peer: ', thisPeerData)
// Temporary business logic.
// Use the first available wallet service detected.
if (_this.state.p2wdbProviders.length === 0) {
_this.state.selectedP2wdbProvider = thisPeer
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
console.log(`---->P2WDB service selected: ${thisPeer}`)
}
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
_this.config.preferredP2wdbProvider &&
thisPeer === _this.config.preferredP2wdbProvider
) {
_this.state.selectedP2wdbProvider = thisPeer
}
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.p2wdbProviders.filter(
(x) => x === thisPeer
)
// Add the peer to the list of serviceProviders if it doesn't already
// exist in the list.
if (!alreadyExists.length) {
_this.state.p2wdbProviders.push(thisPeer)
}
}
}
} catch (err) {
console.error('Error in pollForP2wdbServices()')
throw err
}
}
// Poll the ipfs-coord coordination channel for available BCH wallet service
// providers. This method is called periodically by a timer-interval.
pollForBchServices () {
try {
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
const peers = _this.ipfsCoord.thisNode.peerList
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
// Array of objects. Each object is the IPFS ID of the peer and contains
// data about that peer.
const peerData = _this.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
for (let i = 0; i < peers.length; i++) {
const thisPeer = peers[i]
const thisData = peerData.filter((x) => x.from === thisPeer)
const thisPeerData = thisData[0]
// Create a 'fingerprint' that defines the wallet service.
const protocol = thisPeerData.data.jsonLd.protocol
const version = thisPeerData.data.jsonLd.version
// console.log(
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`,
// )
let versionMatches = false
if (version) {
versionMatches = _this.semver.gt(version, MIN_BCH_WALLET_VERSION)
}
// Ignore any peers that don't match the fingerprint for a BCH wallet
// service.
if (protocol && protocol.includes(WALLET_PROTOCOL) && versionMatches) {
// console.log('Matching peer: ', thisPeerData)
// Temporary business logic.
// Use the first available wallet service detected.
if (_this.state.serviceProviders.length === 0) {
_this.state.selectedServiceProvider = thisPeer
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
console.log(`---->BCH wallet service selected: ${thisPeer}`)
}
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
_this.config.preferredProvider &&
thisPeer === _this.config.preferredProvider
) {
_this.state.selectedServiceProvider = thisPeer
}
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.serviceProviders.filter(
(x) => x === thisPeer
)
// Add the peer to the list of serviceProviders if it doesn't already
// exist in the list.
if (!alreadyExists.length) {
_this.state.serviceProviders.push(thisPeer)
}
}
}
} catch (err) {
console.error('Error in pollForBchServices(): ', err)
// Do not throw error. This is a top-level function.
}
}
// 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)
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.
}
}
}
module.exports = IpfsCoordAdapter
+36 -1
View File
@@ -11,10 +11,13 @@
// Global npm libraries
// const IPFS = require('ipfs')
const IPFS = require('@chris.troutner/ipfs')
const fs = require('fs')
// Local libraries
const config = require('../../../config')
const IPFS_DIR = './.ipfsdata/ipfs'
class IpfsAdapter {
constructor (localConfig) {
// Encapsulate dependencies
@@ -23,6 +26,7 @@ class IpfsAdapter {
// Properties of this class instance.
this.isReady = false
this.config = config
this.fs = fs
}
// Start an IPFS node.
@@ -30,7 +34,7 @@ class IpfsAdapter {
try {
// Ipfs Options
const ipfsOptions = {
repo: './.ipfsdata/ipfs',
repo: IPFS_DIR,
start: true,
config: {
relay: {
@@ -51,6 +55,11 @@ class IpfsAdapter {
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
]
},
Datastore: {
StorageMax: '2GB',
StorageGCWatermark: 50,
GCPeriod: '15m'
}
}
}
@@ -71,6 +80,12 @@ class IpfsAdapter {
return this.ipfs
} catch (err) {
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()
}
throw err
}
}
@@ -80,6 +95,26 @@ 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
}
}
}
module.exports = IpfsAdapter
+268
View File
@@ -0,0 +1,268 @@
/*
This library contains business-logic for dealing with P2WDB service
providers. Most of these functions are called by the /p2wdb REST API endpoints.
*/
// Public npm libraries
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
// Local libraries
const { wlogger } = require('../wlogger')
let _this
class P2wdbAdapter {
constructor (localConfig = {}) {
// console.log('BCH localConfig: ', localConfig)
this.ipfs = localConfig.ipfs
if (!this.ipfs) {
throw new Error(
'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.'
)
}
// Connect the RPC handler when the event fires with new data.
this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
this.jsonrpc = jsonrpc
// A queue for holding RPC data that has arrived.
this.rpcDataQueue = []
_this = this // Global handle on instance of this Class.
}
// This handler is triggered when RPC data comes in over IPFS.
// Handle RPC input, and add the response to the RPC queue.
// Once in the queue, it will get processed by waitForRPCResponse()
rpcHandler (data) {
try {
// Convert string input into an object.
// const jsonData = JSON.parse(data)
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.
}
}
// Get the status of BCH wallet services this node can talk to. Returns an
// array of BCH wallet service providers.
async getStatus () {
try {
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
// const status = {
// state: this.ipfs.ipfsCoordAdapter.state
// }
// Add names to the IPFS IDs for each provider.
const initialServiceProviders =
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
const serviceProviders = []
for (let i = 0; i < initialServiceProviders.length; i++) {
const thisProvider = initialServiceProviders[i]
const providerData = peerData.filter((x) => x.from === thisProvider)
if (providerData.length) {
const provObj = {
ipfsId: thisProvider,
name: providerData[0].data.jsonLd.name
}
serviceProviders.push(provObj)
}
}
// console.log(
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
// )
const outObj = {
serviceProviders,
selectedProvider:
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
}
// console.log('outObj: ', outObj)
return outObj
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/p2wdb.js/getStatus()')
throw err
}
}
// Choose the BCH wallet service to use.
async selectProvider (providerId) {
try {
this.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider = providerId
return true
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/p2wdb/getStatus()')
throw err
}
}
// Read an entry from the P2WDB, given an entry hash.
async getEntryByHash (hash) {
try {
// Input validation.
if (!hash || typeof hash !== 'string') {
throw new Error('getEntry() input hash must be a string.')
}
// 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
if (!selectedProvider) {
throw new Error('No P2WDB Service provider available yet.')
}
const rpcData = {
endpoint: 'getByHash',
hash
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'p2wdb', 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.error('Error in getEntryByHash()')
throw err
}
}
async writeEntry (writeObj) {
try {
const { txid, signature, message, data } = writeObj
// 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
if (!selectedProvider) {
throw new Error('No P2WDB Service provider available yet.')
}
const rpcData = {
endpoint: 'write',
txid,
signature,
message,
data
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'p2wdb', 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 result = await this.waitForRPCResponse(rpcId)
return result
} catch (err) {
console.error('Error in adapters/p2wdb/writeEntry()')
throw err
}
}
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
// Initialize variables for tracking the return data.
let dataFound = false
let cnt = 0
// Default return value, if the remote computer does not respond in time.
let data = {
success: false,
message: 'request timed out',
data: ''
}
// Loop that waits for a response from the service provider.
do {
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
for (let i = 0; i < this.rpcDataQueue.length; i++) {
const rawData = this.rpcDataQueue[i]
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
if (rawData.payload.id === rpcId) {
dataFound = true
// console.log('data was found in the queue')
data = rawData.payload.result.value
// Remove the data from the queue
this.rpcDataQueue.splice(i, 1)
break
}
}
// Wait between loops.
// await this.sleep(1000)
await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
cnt++
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
return data
} catch (err) {
console.error('Error in waitForRPCResponse()')
throw err
}
}
}
module.exports = P2wdbAdapter
+9 -2
View File
@@ -5,6 +5,7 @@
*/
// Public npm libraries.
const EventEmitter = require('events')
// Load the Clean Architecture Adapters library
const Adapters = require('../adapters')
@@ -21,8 +22,14 @@ const RESTControllers = require('./rest-api')
class Controllers {
constructor (localConfig = {}) {
this.adapters = new Adapters()
this.useCases = new UseCases({ adapters: this.adapters })
// Initialize EventEmitters used to pass event-driven data around the app.
this.eventEmitter = new EventEmitter()
localConfig.eventEmitter = this.eventEmitter
// Initialize Clean Architecture classes. Inject dependencies.
this.adapters = new Adapters(localConfig)
localConfig.adapters = this.adapters
this.useCases = new UseCases(localConfig)
}
async attachControllers (app) {
+12 -2
View File
@@ -62,6 +62,7 @@ class JSONRPC {
// Attempt to parse the incoming data as a JSON RPC string.
const parsedData = _this.jsonrpc.parse(str)
// wlogger.debug(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
// console.log(`parsedData; ${JSON.stringify(parsedData, null, 2)}`)
// Exit quietly if the incoming string is an invalid JSON RPC string.
if (parsedData.type === 'invalid') {
@@ -71,6 +72,7 @@ class JSONRPC {
// Check for duplicate entries with same 'id' value.
const alreadyProcessed = _this._checkIfAlreadyProcessed(parsedData)
// console.log(`alreadyProcessed: ${alreadyProcessed}`)
if (alreadyProcessed) {
return false
} else {
@@ -81,14 +83,18 @@ class JSONRPC {
// up to this library. Ignore these messages.
if (
parsedData.type.includes('success') &&
parsedData.payload.method === undefined
// parsedData.payload.method === undefined
parsedData.payload.id.includes('metrics')
) {
return false
}
// Process response from REST API.
_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}, method: ${parsedData.payload.method}`
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
)
}
@@ -109,6 +115,10 @@ class JSONRPC {
break
case 'about':
retObj = await _this.aboutController.aboutRouter(parsedData)
break
// default:
// // Process response from REST API.
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
}
// console.log('retObj: ', retObj)
+497
View File
@@ -0,0 +1,497 @@
/*
REST API Controller library for the /bch route
*/
const { wlogger } = require('../../../adapters/wlogger')
// let _this
class BchRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /bch REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /bch REST Controller.'
)
}
// Encapsulate dependencies
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
}
/**
* @api {get} /bch Get status on BCH infrastructure
* @apiPermission public
* @apiName GetBchStatus
* @apiGroup REST BCH
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/bch
*
*/
async getStatus (ctx) {
try {
const status = await this.adapters.bch.getStatus()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in bch/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/provider Provider
* @apiPermission public
* @apiName Provider
* @apiGroup REST BCH
* @apiDescription Select a different BCH wallet service on the IPFS network,
* for interacting with the BCH blockchain.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "provider": "QmXtHADckCmT6jodpAgn3TcDQWjC29gQd2fKKHDTpo8DJT" }' localhost:5001/bch/provider
*/
async postProvider (ctx) {
try {
const providerId = ctx.request.body.provider
await this.adapters.bch.selectProvider(providerId)
const body = {
success: true
}
ctx.body = body
} catch (err) {
wlogger.error('Error in bch/controller.js/postProvider(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/balance Balance
* @apiName Balance
* @apiGroup REST BCH
* @apiDescription Returns the BCH balance for an array of addresses.
*
* Given the 'addresses' property returns an array of objects
* with the following properties
*
* - success : - Petition status
* - balances : [] - Balance of the provided addresses
* - balance : {} - Object with the balance types of an address
* - confirmed : - Confirmed balance
* - unconfirmed : - Unconfirmed Balance
* - address : "" - Address related to the balance
*
* 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/balance
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "success":true,
* "balances":[
* {
* "balance":{
* "confirmed":1000,
* "unconfirmed":0
* },
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
* }
* ]
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async balance (ctx) {
try {
const addrs = ctx.request.body.addresses
const data = await this.adapters.bch.getBalances(addrs)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/utxos UTXOs
* @apiName UTXOs
* @apiGroup REST BCH
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
* with token information.
*
* Given an address, this endpoint will return an object with thre following
* properties:
*
* - address: "" - the address these UTXOs are associated with
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
* not spent, to be safe.
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
* - type1: {}
* - tokens: [] - SLP token Type 1 tokens.
* - mintBatons: [] - SLP token Type 1 mint batons.
* - nft: {}
* - tokens: [] - NFT tokens
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* [
* {
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj",
* "bchUtxos":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "tx_pos":0,
* "value":1000,
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "vout":0,
* "isValid":false
* }
* ],
* "nullUtxos":[
*
* ],
* "slpUtxos":{
* "type1":{
* "mintBatons":[
*
* ],
* "tokens":[
*
* ]
* },
* "nft":{
* "groupMintBatons":[
*
* ],
* "groupTokens":[
*
* ],
* "tokens":[
*
* ]
* }
* }
* }
* ]
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async utxos (ctx) {
try {
const address = ctx.request.body.address
// console.log('address: ', address)
const utxos = await this.adapters.bch.getUtxos(address)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = [utxos]
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/broadcast Broadcast
* @apiName Broadcast
* @apiGroup REST BCH
* @apiDescription Broadcast a transaction to the BCH network.
* The transaction should be encoded as a hexidecimal string.
*
* This endpoint will return a transaction id
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000" }' localhost:5001/bch/broadcast
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async broadcast (ctx) {
try {
const hex = ctx.request.body.hex
const txid = await this.adapters.bch.broadcast(hex)
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
ctx.body = txid
} catch (err) {
this.handleError(ctx, err)
}
}
/**
*
* @api {post} /bch/transactions Transactions
* @apiName Transactions
* @apiGroup REST BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
*
* Given the 'addresses' property returns an array of objects
* with the following properties
*
* - success : - Petition status
* - transactions : [] - Transaction of the provided address
* - transactions: [] - Transaction details
* - height : - Reference to the blockchain size
* - tx_hash: "" - Transaction hash
* - address : "" - Address associated to the transactions
*
* 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
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "success":true,
* "transactions":[
* {
* "transactions":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
* }
* ],
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
* }
* ]
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async transactions (ctx) {
try {
const addr = ctx.request.body.address
const data = await this.adapters.bch.getTransactions(addr)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/transaction Transaction
* @apiName Transaction
* @apiGroup REST BCH
* @apiDescription Get data about a specific transaction.
* Given a transaction id this endpoint will return an object
* with the following properties
*
* - txid: "" - Transaction ID
* - hash: "" - Transaction hash
* - version: - Version number
* - size: - Transaction size
* - locktime: -
* - vin: [] - Transaction inputs
* - vout: [] - Transaction outputs
* - hex: "" - hexadecimal script
* - blockhash: "" - Reference to the block register
* - confirmations : "" - Transaction confirmations
* - time: - Execution time
* - blocktime: - Execution time
* - 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
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
* "version":1,
* "size":272,
* "locktime":0,
* "vin":[
* {
* "txid":"4deef6de4b973706cd6e8fc8105a41a84be349e4e9717225ee5e7c63538e95e8",
* "vout":1,
* "scriptSig":{
* "asm":"3045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f2496[ALL|FORKID] 0467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3",
* "hex":"483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3"
* },
* "sequence":4294967295,
* "address":"bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq",
* "value":0.00001824
* }
* ],
* "vout":[
* {
* "value":0,
* "n":0,
* "scriptPubKey":{
* "asm":"OP_RETURN -385055325 46226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
* "hex":"6a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
* "type":"nulldata"
* }
* },
* {
* "value":0.0000155,
* "n":1,
* "scriptPubKey":{
* "asm":"OP_DUP OP_HASH160 066ebee590278f32aedc8a4865700c49e717f1d7 OP_EQUALVERIFY OP_CHECKSIG",
* "hex":"76a914066ebee590278f32aedc8a4865700c49e717f1d788ac",
* "reqSigs":1,
* "type":"pubkeyhash",
* "addresses":[
* "bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq"
* ]
* }
* }
* ],
* "hex":"0100000001e8958e53637c5eee257271e9e449e34ba8415a10c88f6ecd0637974bdef6ee4d010000008b483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3ffffffff020000000000000000276a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a0e060000000000001976a914066ebee590278f32aedc8a4865700c49e717f1d788ac00000000",
* "blockhash":"0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536",
* "confirmations":97988,
* "time":1568338904,
* "blocktime":1568338904,
* "isValidSLPTx":false
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async transaction (ctx) {
try {
const txid = ctx.request.body.txid
const data = await this.adapters.bch.getTransaction(txid)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {REST} /bch/pubkey PubKey
* @apiPermission public
* @apiName PubKey
* @apiGroup REST BCH
* @apiDescription Get the public key from an address.
* Given an address the endpoint will return an object with the
* following properties
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - success: - Request status
* - publickey: - Address public key
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/pubkey
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "success": true,
* "status": 200,
* "endpoint": "pubkey",
* "pubkey": {
* "success": true,
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
* }
* }
*/
async pubKey (ctx) {
try {
const address = ctx.request.body.address
const data = await this.adapters.bch.getPubKey(address)
console.log(`pubkey 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.
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 = BchRESTControllerLib
+101
View File
@@ -0,0 +1,101 @@
/*
REST API library for /bch route.
*/
// Public npm libraries.
const Router = require('koa-router')
// Local libraries.
const BchRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
let _this
class BchRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating BCH REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating BCH REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.bchRESTController = new BchRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/bch'
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('/', this.getStatus)
this.router.post('/provider', this.postProvider)
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('/pubkey', this.postPubKey)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
async getStatus (ctx, next) {
await _this.bchRESTController.getStatus(ctx, next)
}
async postProvider (ctx, next) {
await _this.bchRESTController.postProvider(ctx, next)
}
async postBalance (ctx, next) {
await _this.bchRESTController.balance(ctx, next)
}
async postUtxos (ctx, next) {
await _this.bchRESTController.utxos(ctx, next)
}
async postBroadcast (ctx, next) {
await _this.bchRESTController.broadcast(ctx, next)
}
async postTransactions (ctx, next) {
await _this.bchRESTController.transactions(ctx, next)
}
async postTransaction (ctx, next) {
await _this.bchRESTController.transaction(ctx, next)
}
async postPubKey (ctx, next) {
await _this.bchRESTController.pubKey(ctx, next)
}
}
module.exports = BchRouter
+15
View File
@@ -11,6 +11,9 @@ const AuthRESTController = require('./auth')
const UserRouter = require('./users')
const ContactRESTController = require('./contact')
const LogsRESTController = require('./logs')
const BchRESTController = require('./bch')
const IpfsRESTController = require('./ipfs')
const P2wdbRESTController = require('./p2wdb')
class RESTControllers {
constructor (localConfig = {}) {
@@ -52,6 +55,18 @@ class RESTControllers {
// Attach the REST API Controllers associated with the /logs route
const logsRESTController = new LogsRESTController(dependencies)
logsRESTController.attach(app)
// Attach the REST API Controllers associated with the /bch route
const bchRESTController = new BchRESTController(dependencies)
bchRESTController.attach(app)
// Attach the REST API Controllers associated with the /ipfs route
const ipfsRESTController = new IpfsRESTController(dependencies)
ipfsRESTController.attach(app)
// Attach the REST API Controllers associated with the /p2wdb route
const p2wdbRESTController = new P2wdbRESTController(dependencies)
p2wdbRESTController.attach(app)
}
}
@@ -0,0 +1,98 @@
/*
REST API Controller library for the /ipfs route
*/
const { wlogger } = require('../../../adapters/wlogger')
let _this
class IpfsRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
)
}
// Encapsulate dependencies
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
_this = this
}
/**
* @api {get} /ipfs Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetIpfsStatus
* @apiGroup REST BCH
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
*
*/
async getStatus (ctx) {
try {
const status = await _this.adapters.ipfs.getStatus()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
}
}
// Return information on IPFS peers this node is connected to.
async getPeers (ctx) {
try {
const showAll = ctx.request.body.showAll
const peers = await _this.adapters.ipfs.getPeers(showAll)
ctx.body = { peers }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getPeers(): ')
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
}
}
// Get data about the known Circuit Relays. Hydrate with data from peers list.
async getRelays (ctx) {
try {
const relays = await _this.adapters.ipfs.getRelays()
ctx.body = { relays }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getRelays(): ')
// ctx.throw(422, err.message)
_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 = IpfsRESTControllerLib
+68
View File
@@ -0,0 +1,68 @@
/*
REST API library for the /ipfs route.
*/
// Public npm libraries.
const Router = require('koa-router')
// Local libraries.
const IPFSRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
// let _this
class BchRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.ipfsRESTController = new IPFSRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/ipfs'
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('/', this.ipfsRESTController.getStatus)
this.router.post('/peers', this.ipfsRESTController.getPeers)
this.router.post('/relays', this.ipfsRESTController.getRelays)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
// async getStatus (ctx, next) {
// await _this.ipfsRESTController.getStatus(ctx, next)
// }
}
module.exports = BchRouter
@@ -0,0 +1,138 @@
/*
REST API Controller library for the /p2wdb route
*/
const { wlogger } = require('../../../adapters/wlogger')
// let _this
class P2wdbRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
)
}
// Encapsulate dependencies
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
}
/**
* @api {get} /p2wdb Get status on P2WDB infrastructure
* @apiPermission public
* @apiName GetP2wdbStatus
* @apiGroup REST BCH
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/p2wdb
*
*/
async getStatus (ctx) {
try {
const status = await this.adapters.p2wdb.getStatus()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in bch/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {post} /p2wdb/provider Provider
* @apiName Provider
* @apiGroup REST P2WDB
* @apiDescription Select a different P2WDB service on the IPFS network, for
* interacting with the P2WDB.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "provider": "QmXtHADckCmT6jodpAgn3TcDQWjC29gQd2fKKHDTpo8DJT" }' localhost:5001/p2wdb/provider
*/
async postProvider (ctx) {
try {
const providerId = ctx.request.body.provider
await this.adapters.p2wdb.selectProvider(providerId)
const body = {
success: true
}
ctx.body = body
} catch (err) {
wlogger.error('Error in p2wdb/controller.js/postProvider(): ', err)
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
// curl -H "Content-Type: application/json" -X POST -d '{ "hash": "zdpuAzNVEHYegiazAmbnQtvQpEvJGsdXDbcH7vHFYx4NWDUqk" }' localhost:5001/p2wdb/entryFromHash
async getEntryByHash (ctx) {
try {
const hash = ctx.request.body.hash
const data = await this.adapters.p2wdb.getEntryByHash(hash)
const body = {
success: true,
data
}
ctx.body = body
} catch (err) {
wlogger.error('Error in p2wdb/controller.js/getEntryByHash(): ', err)
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
async writeEntry (ctx) {
try {
const txid = ctx.request.body.txid
const signature = ctx.request.body.signature
const message = ctx.request.body.message
const data = ctx.request.body.data
const writeObj = { txid, signature, message, data }
const hash = await this.adapters.p2wdb.writeEntry(writeObj)
ctx.body = {
success: true,
hash
}
} catch (err) {
wlogger.error('Error in p2wdb/controller.js/writeEntry(): ', 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 = P2wdbRESTControllerLib
+101
View File
@@ -0,0 +1,101 @@
/*
REST API library for /p2wdb route.
*/
// Public npm libraries.
const Router = require('koa-router')
// Local libraries.
const P2wdbRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
let _this
class P2wdbRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating P2WDB REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating P2WDB REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.p2wdbRESTController = new P2wdbRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/p2wdb'
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('/', this.getStatus)
this.router.post('/provider', this.postProvider)
this.router.post('/entryFromHash', this.entryFromHash)
this.router.post('/write', this.write)
// this.router.post('/broadcast', this.postBroadcast)
// this.router.post('/transactions', this.postTransactions)
// this.router.post('/transaction', this.postTransaction)
// this.router.post('/pubkey', this.postPubKey)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
async getStatus (ctx, next) {
await _this.p2wdbRESTController.getStatus(ctx, next)
}
async postProvider (ctx, next) {
await _this.p2wdbRESTController.postProvider(ctx, next)
}
async entryFromHash (ctx, next) {
await _this.p2wdbRESTController.getEntryByHash(ctx, next)
}
async write (ctx, next) {
await _this.p2wdbRESTController.writeEntry(ctx, next)
}
// async postBroadcast (ctx, next) {
// await _this.bchRESTController.broadcast(ctx, next)
// }
//
// async postTransactions (ctx, next) {
// await _this.bchRESTController.transactions(ctx, next)
// }
//
// async postTransaction (ctx, next) {
// await _this.bchRESTController.transaction(ctx, next)
// }
//
// async postPubKey (ctx, next) {
// await _this.bchRESTController.pubKey(ctx, next)
// }
}
module.exports = P2wdbRouter
+6
View File
@@ -14,6 +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.'
)
}
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
+3 -2
View File
@@ -13,7 +13,8 @@ const context = {}
const UserController = require('../../../src/controllers/rest-api/users/controller')
const Adapters = require('../../../src/adapters')
const adapters = new Adapters()
const EventEmitter = require('events')
const adapters = new Adapters({ eventEmitter: new EventEmitter() })
const UseCases = require('../../../src/use-cases/')
let uut
let sandbox
@@ -50,7 +51,7 @@ describe('Users', () => {
})
beforeEach(() => {
const useCases = new UseCases({ adapters })
const useCases = new UseCases({ adapters, eventEmitter: new EventEmitter() })
uut = new UserController({ adapters, useCases })
sandbox = sinon.createSandbox()
+5
View File
@@ -0,0 +1,5 @@
# Manual End-to-End Tests
Each test in this directory is used to debug live systems.
Read the directions at the top of each test file.
Run each test with `node <testname>.js`
+149
View File
@@ -0,0 +1,149 @@
/*
To run this end-to-end test:
- Start the REST API server with `npm start`, wait for it to find a BCH wallet server.
- Run this test app with `mocha --timeout=30000 bch-e2e.js`
This test will query each REST API endpoint and ensure they are working correctly.
*/
const SERVER = 'http://localhost:5001'
const axios = require('axios')
const assert = require('chai').assert
describe('#BCH E2E Tests', () => {
console.log(
'Ensure the REST API app has been started and has found a BCH wallet service provider, before running this test.'
)
describe('#status', () => {
it('should report at least one service provider', async () => {
const url = `${SERVER}/bch`
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)
})
})
describe('#balance', () => {
it('should get the balance for an address', async () => {
const body = {
addresses: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
}
const url = `${SERVER}/bch/balance`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.success, true)
assert.equal(result.data.status, 200)
assert.property(result.data, 'balance')
assert.property(result.data.balance, 'confirmed')
assert.property(result.data.balance, 'unconfirmed')
})
})
describe('#utxos', () => {
it('should get UTXOs for an address', async () => {
const body = {
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
}
const url = `${SERVER}/bch/utxos`
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')
})
})
describe('#broadcast', () => {
it('should attempt to broadcast a transaction', async () => {
const body = {
hex: '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
}
const url = `${SERVER}/bch/broadcast`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.status, 422)
assert.equal(result.data.success, false)
assert.equal(result.data.message, 'TX decode failed')
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 = {
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
}
const url = `${SERVER}/bch/pubkey`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.success, true)
assert.equal(result.data.status, 200)
assert.equal(result.data.endpoint, 'pubkey')
assert.equal(result.data.pubkey.success, false)
assert.equal(result.data.pubkey.publicKey, 'not found')
})
it('should get a public key for an address', async () => {
const body = {
address: 'bitcoincash:qznxh6yegf7nfrsd0zeksed4jcqzvg7tzqlcqw3v97'
}
const url = `${SERVER}/bch/pubkey`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.success, true)
assert.equal(result.data.status, 200)
assert.equal(result.data.endpoint, 'pubkey')
assert.equal(result.data.pubkey.success, true)
assert.equal(
result.data.pubkey.publicKey,
'03ebd6d6aae05da1c5905445e3886b30f6a31b26a88de5980de236bd22380fa942'
)
})
})
})
+287
View File
@@ -0,0 +1,287 @@
/*
Unit tests for the src/lib/users.js business logic library.
TODO: verify that an admin can change the type of a user
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const EventEmitter = require('events')
const cloneDeep = require('lodash.clonedeep')
// Local support libraries
// const testUtils = require('../../utils/test-utils')
// Unit under test (uut)
const BchAdapter = require('../../../src/adapters/bch')
const adapters = require('../mocks/adapters')
const eventEmitter = new EventEmitter()
const mockDataLib = require('../mocks/use-cases/rest-api-mocks')
describe('#bch-use-case', () => {
let uut
let sandbox
let mockData
before(async () => {
// Delete all previous users in the database.
// await testUtils.deleteAllUsers()
})
beforeEach(() => {
sandbox = sinon.createSandbox()
mockData = cloneDeep(mockDataLib)
const ipfs = adapters.ipfs
uut = new BchAdapter({ ipfs, eventEmitter })
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if ipfs is not passed in', () => {
try {
uut = new BchAdapter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
)
}
})
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', () => {
it('should resolve when data is received', async () => {
// Mock dependencies
uut.ipfs.ipfsCoordAdapter.bchjs = {
Util: {
sleep: () => {}
}
}
// Mock data.
const rpcId = '123'
uut.rpcDataQueue.push(mockData.rpcData)
const result = await uut.waitForRPCResponse(rpcId)
// console.log('result: ', result)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'balances')
assert.isArray(result.balances)
})
it('should catch and throw an error', async () => {
try {
// Force an error
uut.ipfs.ipfsCoordAdapter.bchjs = {
Util: {
sleep: () => {
throw new Error('test error')
}
}
}
// Mock data.
const rpcId = '123'
uut.rpcDataQueue.push(mockData.rpcData)
await uut.waitForRPCResponse(rpcId)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('error: ', err)
assert.include(err.message, 'test error')
}
})
})
describe('#rpcHandler', () => {
it('should add RPC data to queue', () => {
const data = {
payload: {
id: '123'
}
}
uut.rpcHandler(data)
})
})
describe('#getStatus', () => {
it('should get the status from ipfs-coord', async () => {
const result = await uut.getStatus()
console.log('result: ', result)
})
})
describe('#getBalances', () => {
it('should get the balance of an address', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addrs = ['addr1']
const result = await uut.getBalances(addrs)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getBalances()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#broadcast', () => {
it('should broadcast a transaction', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const hex = 'abc123'
const result = await uut.broadcast(hex)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.broadcast()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getTransactions', () => {
it('should get transactions history for an address', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addr = 'abc123'
const result = await uut.getTransactions(addr)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getTransactions()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getTransaction', () => {
it('should get transaction details for a txid', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const txid = 'abc123'
const result = await uut.getTransaction(txid)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getTransaction()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getPubKey', () => {
it('should get transaction details for a txid', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addr = 'abc123'
const result = await uut.getPubKey(addr)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getPubKey()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
})
+71 -2
View File
@@ -4,23 +4,34 @@
const assert = require('chai').assert
const sinon = require('sinon')
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 mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
describe('#IPFS', () => {
let uut
let sandbox
let mockData
beforeEach(() => {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs })
uut = new IPFSCoordAdapter({ ipfs, eventEmitter: new EventEmitter() })
sandbox = sinon.createSandbox()
mockData = cloneDeep(mockDataLib)
})
afterEach(() => sandbox.restore())
afterEach(() => {
sandbox.restore()
clearInterval(uut.pollBchServiceInterval)
clearInterval(uut.pollP2wdbServiceInterval)
})
describe('#constructor', () => {
it('should throw an error if ipfs instance is not included', () => {
@@ -35,6 +46,20 @@ 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.'
)
}
})
})
describe('#start', () => {
@@ -100,4 +125,48 @@ describe('#IPFS', () => {
}
})
})
describe('#pollForBchServices', () => {
it('should find and select the wallet service', () => {
uut.ipfsCoord = {
thisNode: {
peerList: mockData.peers,
peerData: mockData.peerData
}
}
uut.pollForBchServices()
// It should fine the service in the mocked data.
assert.equal(
uut.state.selectedServiceProvider,
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
)
})
it('should catch and report errors', () => {
uut.pollForBchServices()
assert.isOk(true, 'Not throwing an error is a success.')
})
})
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.')
})
})
})
@@ -8,13 +8,14 @@ const sinon = require('sinon')
const IPFSLib = require('../../../src/adapters/ipfs')
const IPFSMock = require('../mocks/ipfs-mock')
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
const EventEmitter = require('events')
describe('#IPFS-adapter-index', () => {
let uut
let sandbox
beforeEach(() => {
uut = new IPFSLib()
uut = new IPFSLib({ eventEmitter: new EventEmitter() })
sandbox = sinon.createSandbox()
})
+21
View File
@@ -60,4 +60,25 @@ describe('#IPFS-adapter', () => {
assert.equal(result, true)
})
})
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')
}
})
})
})
+10 -1
View File
@@ -74,10 +74,17 @@ describe('#JSON RPC', () => {
})
it('should return default response if routing is not possible', async () => {
// Force desired code path
// sandbox
// .stub(uut.adapters.ipfs.ipfsCoordAdapter, 'peerInputHandler')
// .returns()
// uut.adapters.ipfs.ipfsCoordAdapter.peer
const id = uid()
const json = jsonrpc.request(id, 'unknownMethod', {})
const str = JSON.stringify(json)
// console.log(`str: ${str}`)
const result = await uut.router(str, 'peerA')
// console.log('result: ', result)
@@ -204,7 +211,9 @@ describe('#JSON RPC', () => {
// Force the RPC type to be 'success', to indicate an RPC that was
// processed internal to ipfs-coord.
sandbox.stub(uut.jsonrpc, 'parse').returns({
payload: {},
payload: {
id: 'metrics5323'
},
type: 'success'
})
@@ -0,0 +1,325 @@
/*
Unit tests for the REST API handler for the /bch endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
const BchController = require('../../../../../src/controllers/rest-api/bch/controller')
let uut
let sandbox
let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#BCH-REST-Controller', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new BchController({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new BchController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating /bch REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new BchController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating /bch REST Controller.'
)
}
})
})
describe('#handleError', () => {
it('should still throw error if there is no message', () => {
try {
const err = {
status: 404
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
})
describe('#getStatus', () => {
it('should return 422 status on arbitrary biz logic error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getStatus')
.rejects(new Error('test error'))
await uut.getStatus(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, 'getStatus').resolves('bch')
await uut.getStatus(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.equal(ctx.response.body.status, 'bch')
})
})
describe('#balance', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getBalances')
.rejects(new Error('test error'))
ctx.request.body = {
addresses: 'blah'
}
await uut.balance(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, 'getBalances').resolves({ status: 200 })
ctx.request.body = {
addresses: 'blah'
}
await uut.balance(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#utxos', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getUtxos')
.rejects(new Error('test error'))
ctx.request.body = {
address: 'blah'
}
await uut.utxos(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, 'getUtxos').resolves({ status: 200 })
ctx.request.body = {
address: 'blah'
}
await uut.utxos(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#broadcast', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'broadcast')
.rejects(new Error('test error'))
ctx.request.body = {
hex: 'blah'
}
await uut.broadcast(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, 'broadcast').resolves({ status: 200 })
ctx.request.body = {
hex: 'blah'
}
await uut.broadcast(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#transactions', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getTransactions')
.rejects(new Error('test error'))
ctx.request.body = {
address: 'blah'
}
await uut.transactions(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, 'getUtxos').resolves({ status: 200 })
ctx.request.body = {
address: 'blah'
}
await uut.transactions(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#transaction', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getTransaction')
.rejects(new Error('test error'))
ctx.request.body = {
txid: 'blah'
}
await uut.transaction(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, 'getTransactions')
.resolves({ status: 200 })
ctx.request.body = {
txid: 'blah'
}
await uut.transaction(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#pubKey', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getPubKey')
.rejects(new Error('test error'))
ctx.request.body = {
addr: 'blah'
}
await uut.pubKey(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, 'getPubKey').resolves({ status: 200 })
ctx.request.body = {
addr: 'blah'
}
await uut.pubKey(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
})
@@ -0,0 +1,78 @@
/*
Unit tests for the REST API handler for the /users endpoints.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local support libraries
const adapters = require('../../../mocks/adapters')
const UseCasesMock = require('../../../mocks/use-cases')
// const app = require('../../../mocks/app-mock')
const BchRouter = require('../../../../../src/controllers/rest-api/bch')
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#Users-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new BchRouter({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
// ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new BchRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating BCH REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new BchRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating BCH REST Controller.'
)
}
})
})
describe('#attach', () => {
it('should throw an error if app is not passed in.', () => {
try {
uut.attach()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Must pass app object when attaching REST API controllers.'
)
}
})
})
})
+47 -1
View File
@@ -12,7 +12,14 @@ const ipfs = {
peer: {
sendPrivateMessage: () => {}
}
},
thisNode: {
peerData: []
}
},
peerInputHandler: () => {},
state: {
serviceProviders: []
}
}
}
@@ -53,4 +60,43 @@ const localdb = {
}
}
module.exports = { ipfs, localdb }
class BchUseCaseMock {
rpcHandler () {
return {}
}
async getStatus () {
return {}
}
async getBalances () {
return {}
}
async getUtxos () {
return {}
}
async broadcast () {
return {}
}
async getTransaction () {
return {}
}
async getTransactions () {
return {}
}
async getPubKey () {
return {}
}
async waitForRPCResponse () {
return {}
}
}
const bch = new BchUseCaseMock()
module.exports = { ipfs, localdb, bch }
@@ -0,0 +1,119 @@
/*
Mock data for the ipfs-coord.unit.js unit tests.
*/
const peers = [
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
]
const peerData = [
{
from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
channel: 'psf-ipfs-coordination-001',
data: {
apiName: 'ipfs-coord-announce',
apiVersion: '1.3.2',
apiInfo:
'You should put an IPFS hash or web URL here to your documentation.',
ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
type: 'node.js',
ipfsMultiaddrs: [
'/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
'/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
],
orbitdb:
'/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417',
circuitRelays: [],
isCircuitRelay: false,
cryptoAddresses: [
{
blockchain: 'BCH',
type: 'cashAddr',
address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37'
},
{
blockchain: 'BCH',
type: 'slpAddr',
address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q'
}
],
encryptPubKey:
'021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df',
jsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'trout-bch-wallet-service-dev',
version: '1.11.12',
protocol: 'bch-wallet',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
},
identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
},
updatedAt: '2021-08-24T23:13:19.306Z'
}
},
{
from: 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
channel: 'psf-ipfs-coordination-001',
data: {
apiName: 'ipfs-coord-announce',
apiVersion: '1.3.2',
apiInfo: 'https://ipfs-service-provider.fullstack.cash/',
ipfsId: 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
type: 'node.js',
ipfsMultiaddrs: [
'/ip4/127.0.0.1/tcp/5668/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
'/ip4/127.0.0.1/tcp/5669/ws/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
'/ip4/172.17.0.4/tcp/5668/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
'/ip4/172.17.0.4/tcp/5669/ws/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
],
orbitdb:
'/orbitdb/zdpuB2H7Eqv63qWnEisR9SkW71h1GHTu32xdvza4gjBtD9AD8/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN21072122',
circuitRelays: [],
isCircuitRelay: false,
cryptoAddresses: [
{
blockchain: 'BCH',
type: 'cashAddr',
address: 'bitcoincash:qq4pk63gngzxnnhne39n0sl7kn2ekhnxngm5fyrgyd'
},
{
blockchain: 'BCH',
type: 'slpAddr',
address: 'simpleledger:qq4pk63gngzxnnhne39n0sl7kn2ekhnxngh0zlkg6n'
}
],
encryptPubKey:
'037676cdeac8376a75c19f62b40aa06feebc788a6704e1a1d13dcf478d090d7205',
jsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'ipfs-bch-wallet-service-dsl',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
},
identifier: 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
},
updatedAt: '2021-08-24T23:13:20.411Z'
}
}
]
module.exports = {
peers,
peerData
}
+21
View File
@@ -31,12 +31,33 @@ class UserUseCaseMock {
}
}
class BchUseCaseMock {
rpcHandler() {
return {}
}
async getStatus() {
return {}
}
async getBalances() {
return {}
}
async waitForRPCResponse() {
return {}
}
}
class UseCasesMock {
constuctor(localConfig = {}) {
// this.user = new UserUseCaseMock(localConfig)
// this.user = new UserUseCaseMock()
// this.bch = new BchUseCaseMock()
}
user = new UserUseCaseMock()
bch = new BchUseCaseMock()
}
module.exports = UseCasesMock
+214
View File
@@ -0,0 +1,214 @@
/*
Mock data for the rest-api.unit.js unit tests.
*/
const rpcData = {
payload: {
id: '123',
result: {
value: {
success: true,
balances: [
{
balance: {},
address: 'addressString'
}
]
}
}
}
}
const mockRelayData = [
{
multiaddr:
'/ip4/139.162.76.54/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
connected: true,
updatedAt: '2021-10-11T15:59:31.701Z',
ipfsId: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
isBootstrap: false,
metrics: {
aboutLatency: [596, 769, 1029]
},
latencyScore: 682
},
{
multiaddr:
'/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
connected: true,
updatedAt: '2021-10-11T15:59:31.701Z',
ipfsId: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
isBootstrap: false,
metrics: {
aboutLatency: [1080, 530, 1137]
},
latencyScore: 805
},
{
multiaddr:
'/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKq',
connected: true,
updatedAt: '2021-10-11T15:59:31.701Z',
ipfsId: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKq',
isBootstrap: false,
metrics: {
aboutLatency: [1080, 530, 1137]
},
latencyScore: 805
}
]
const mockPeerData = [
{
from: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
channel: 'psf-ipfs-coordination-001',
data: {
apiName: 'ipfs-coord-announce',
apiVersion: '1.3.2',
apiInfo:
'You should put an IPFS hash or web URL here to your documentation.',
broadcastedAt: '2021-10-11T15:59:59.598Z',
ipfsId: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
type: 'node.js',
ipfsMultiaddrs: [
'/ip4/127.0.0.1/tcp/5268/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
'/ip4/127.0.0.1/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
'/ip4/139.162.76.54/tcp/5268/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
'/ip4/139.162.76.54/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77'
],
orbitdb:
'/orbitdb/zdpuApooyo2rWMuDkuTrEcxQghJKzmyQSpfUEvBhx45UNgLJX/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK7721101115',
circuitRelays: [],
isCircuitRelay: true,
cryptoAddresses: [
{
blockchain: 'BCH',
type: 'cashAddr',
address: 'bitcoincash:qpsaewu50nnm9gjn7e0ejmusrflfd8lrdy8szg2564'
},
{
blockchain: 'BCH',
type: 'slpAddr',
address: 'simpleledger:qpsaewu50nnm9gjn7e0ejmusrflfd8lrdyttfnl5yt'
}
],
encryptPubKey:
'0338122208e2842f39afabd23deadbe2acfdcbe69d127c24835e05603771f1e85d',
jsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'ipfs-relay-tokyo-pfs-0945772',
version: '1.3.0',
protocol: 'generic-service',
description:
'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
documentation: 'https://ipfs-service-provider.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
},
identifier: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77'
},
updatedAt: '2021-10-11T15:59:59.857Z'
}
},
{
from: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
channel: 'psf-ipfs-coordination-001',
data: {
apiName: 'ipfs-coord-announce',
apiVersion: '1.3.2',
apiInfo:
'You should put an IPFS hash or web URL here to your documentation.',
broadcastedAt: '2021-10-11T15:59:51.277Z',
ipfsId: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
type: 'node.js',
ipfsMultiaddrs: [
'/ip4/10.124.0.2/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/10.124.0.2/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/10.48.0.5/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/10.48.0.5/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/127.0.0.1/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/127.0.0.1/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/143.198.60.119/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
'/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp'
],
orbitdb:
'/orbitdb/zdpuAxU8itEdbqjDxCeF3C5pRN9HSXmtivJi7ygMhnew3nAPf/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp21101011',
circuitRelays: [],
isCircuitRelay: true,
circuitRelayInfo: {
ip4: '143.198.60.119',
tcpPort: '4001',
crDomain: 'relayer.fullstackcash.nl'
},
cryptoAddresses: [
{
blockchain: 'BCH',
type: 'cashAddr',
address: 'bitcoincash:qzyvlftxcmwwca6qgg3l0zqqrk33hlcxhcpuv84awv'
},
{
blockchain: 'BCH',
type: 'slpAddr',
address: 'simpleledger:qzyvlftxcmwwca6qgg3l0zqqrk33hlcxhcd88uqasj'
}
],
encryptPubKey:
'038264a85c945601ce96a5727f6ec524f16b3228fffd7c2d26ce047111be5598ad',
jsonLd: {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'trout-dev-railgun-relay',
version: '1.0.1',
protocol: 'railgun-relayer',
description:
'This is a generic Railgun Relayer. It has not been customized.',
documentation: 'https://www.railgun.org/',
provider: {
'@type': 'Organization',
name: 'Railgun DAO',
url: 'https://www.railgun.org/'
},
identifier: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp'
},
updatedAt: '2021-10-11T15:59:51.430Z'
}
}
]
const ipfsMockPeers = [
{
addr: '/ip4/139.162.76.54/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
peer: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
direction: 'outbound',
muxer: '/mplex/6.7.0',
latency: 'n/a',
streams: []
},
{
addr: '/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
peer: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
direction: 'outbound',
muxer: '/mplex/6.7.0',
latency: 'n/a',
streams: []
},
// This data is used to exercise some of the exception code paths.
{
addr: '/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKo',
peer: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKo',
direction: 'outbound',
muxer: '/mplex/6.7.0',
latency: 'n/a',
streams: []
}
]
module.exports = {
rpcData,
mockRelayData,
mockPeerData,
ipfsMockPeers
}
+2 -1
View File
@@ -5,6 +5,7 @@
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const EventEmitter = require('events')
// Local support libraries
// const testUtils = require('../../utils/test-utils')
@@ -25,7 +26,7 @@ describe('#use-cases', () => {
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new UseCases({ adapters })
uut = new UseCases({ adapters, eventEmitter: new EventEmitter() })
})
afterEach(() => sandbox.restore())