Compare commits

...
56 Commits
Author SHA1 Message Date
Chris Troutner c352c3d6d1 Merge pull request #11 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-01-01 21:31:14 -08:00
Chris Troutner 5bd425a3d3 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-01-01 21:19:25 -08:00
Chris Troutner 1fcc92e10d Merge pull request #71 from Permissionless-Software-Foundation/ct-unstable
fix(ipsf-coord): Updating to v6.8.10
2022-01-01 21:01:14 -08:00
Chris Troutner f332aece98 fix(ipsf-coord): Updating to v6.8.10 2022-01-01 20:58:56 -08:00
Chris Troutner 89f24e9940 Merge pull request #70 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Switching to private network. Updating ipfs-coord
2022-01-01 20:40:23 -08:00
Chris Troutner 25a75dc369 fix(ipfs-coord): Switching to private network. Updating ipfs-coord to v6.8.9 2022-01-01 20:25:40 -08:00
Chris Troutner f36ce14087 fix(ipfs-coord): Updating v6.8.8 2022-01-01 18:58:47 -08:00
Chris Troutner df45792f47 fix(ipfs-coord): Updating to v6.8.7 2022-01-01 18:43:24 -08:00
Chris Troutner 9e71e33ccd Merge pull request #69 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v6.8.6
2022-01-01 18:20:30 -08:00
Chris Troutner c7b0b0c1d5 fix(ipfs-coord): Updating to v6.8.6 2022-01-01 18:19:07 -08:00
Chris Troutner e475e5f4c3 Downgrading ipfs-http-client 2022-01-01 16:41:56 -08:00
Chris Troutner 18964d3ee4 Merge pull request #10 from Permissionless-Software-Foundation/ct-unstable
Switching to external go-ipfs as default
2022-01-01 07:40:33 -08:00
Chris Troutner 197ece4d0d Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-01-01 07:39:10 -08:00
Chris Troutner a52798b1b7 fix(ipfs-coord): Updating to v6.8.5 2022-01-01 07:37:42 -08:00
Chris Troutner 0748106637 Merge pull request #68 from Permissionless-Software-Foundation/ct-unstable
fix(production): using explcit v0.11.0 ipfs docker container
2022-01-01 06:41:36 -08:00
Chris Troutner 27dcc219b9 fix(production): using explcit v0.11.0 ipfs docker container 2022-01-01 06:40:15 -08:00
Chris Troutner b231686a64 Merge pull request #67 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v6.8.5
2021-12-31 18:40:55 -08:00
Chris Troutner 60668430f5 fix(ipfs-coord): Updating to v6.8.5 2021-12-31 18:38:17 -08:00
Chris Troutner 37ab66ef23 updated ipfs-coord 2021-12-31 16:30:38 -08:00
Chris Troutner 3382e03e69 Debug level 2 by default 2021-12-31 16:23:26 -08:00
Chris Troutner b9680508db Merge pull request #66 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v6.8.4
2021-12-31 16:01:32 -08:00
Chris Troutner acebfdef09 fix(ipfs-coord): Updating to v6.8.4 2021-12-31 15:59:06 -08:00
Chris Troutner 3979694109 Merge pull request #65 from Permissionless-Software-Foundation/ct-unstable
fix(production): Adding instructions for customizing IPFS docker cont…
2021-12-31 15:38:39 -08:00
Chris Troutner ae888d094b fix(production): Adding instructions for customizing IPFS docker container 2021-12-31 15:34:40 -08:00
Chris Troutner e8a3234dcd Merge pull request #64 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): updating to v6.8.2
2021-12-31 12:13:13 -08:00
Chris Troutner fc717e0dcd fix(ipfs-coord): updating to v6.8.2 2021-12-31 12:11:47 -08:00
Chris Troutner 17f3afbf1f fix(e2e tests): Disabling IPFS controllers and adapters for e2e tests 2021-12-30 10:45:20 -08:00
Chris Troutner 18cec75f41 Merge pull request #63 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v6.8.1
2021-12-29 19:33:54 -08:00
Chris Troutner 7621842952 fix(ipfs-coord): Updating to v6.8.1 2021-12-29 19:32:39 -08:00
Chris Troutner 1e27b477a0 feat(ipfs): Updating to js-ipfs v0.60.0 2021-12-29 19:05:45 -08:00
Chris Troutner 08ec38e3a5 changed port 2021-12-29 18:50:09 -08:00
Chris Troutner 0e9a57a1da Merge pull request #62 from Permissionless-Software-Foundation/ct-unstable
Kicking off v2.0.0
2021-12-29 18:04:29 -08:00
Chris Troutner e374522725 fix(version): Bumping to v2.0.0 2021-12-29 18:03:09 -08:00
Chris Troutner e402f33132 Adding more properties to docker-compose 2021-12-29 18:00:17 -08:00
Chris Troutner e85655c0ec Adding data/ directory to gitignore 2021-12-29 17:46:52 -08:00
Chris Troutner 227ffbc4bc Removing data directory 2021-12-29 17:46:28 -08:00
Chris Troutner b41f819783 fix(startup): Exiting if server can't stop 2021-12-29 17:46:05 -08:00
Chris Troutner d6c3635c6a fix(ipfs): Got running with external IPFS node 2021-12-29 17:28:35 -08:00
Chris Troutner 37e98e4248 fix(ipfs): Switching from @chris.troutner/ipfs to ipfs v0.60.0 2021-12-29 16:59:32 -08:00
Chris Troutner 6b23d00ffe Merge pull request #9 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord.js): better error handling
2021-12-24 18:56:51 -08:00
Chris Troutner 53cd4650bd fix(ipfs-coord.js): better error handling 2021-12-24 17:59:52 -08:00
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
23 changed files with 13858 additions and 13987 deletions
+1
View File
@@ -67,5 +67,6 @@ orbitdb
ipfsdata
.ipfsdata
run-dev.sh
data/
!README.md
+10
View File
@@ -104,8 +104,18 @@ class Server {
return app
} catch (err) {
console.error('Could not start server. Error: ', err)
console.log(
'Exiting after 5 seconds. Depending on process manager to restart.'
)
await sleep(5000)
process.exit(1)
}
}
}
function sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
module.exports = Server
+17 -4
View File
@@ -15,7 +15,7 @@ const ipfsCoordName = process.env.COORD_NAME
module.exports = {
// Configure TCP port.
port: process.env.PORT || 5001,
port: process.env.PORT || 5005,
// Password for HTML UI that displays logs.
logPass: 'test',
@@ -72,18 +72,31 @@ module.exports = {
},
// IPFS Ports
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4003,
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4101,
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4103,
// 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,
// 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
: '',
// Settings for production, using external go-ipfs node.
isProduction: process.env.SVC_ENV === 'production' ? true : false,
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
ipfsApiPort: process.env.IPFS_API_PORT
? parseInt(process.env.IPFS_API_PORT)
: 5001
}
+13002 -13928
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -23,12 +23,13 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
"dependencies": {
"@chris.troutner/ipfs": "2.0.2",
"@psf/bch-js": "4.20.26",
"@psf/bch-js": "4.20.28",
"axios": "0.21.1",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs-coord": "6.7.4",
"ipfs": "0.60.0",
"ipfs-coord": "6.8.10",
"ipfs-http-client": "55.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -42,7 +43,7 @@
"koa-passport": "4.1.3",
"koa-router": "10.0.0",
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.0",
"koa2-ratelimit": "0.9.1",
"line-reader": "0.4.0",
"mongoose": "5.11.15",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
@@ -64,6 +65,7 @@
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "4.3.8",
"lodash.clonedeep": "^4.5.0",
"mocha": "8.2.1",
"nyc": "15.1.0",
"semantic-release": "17.4.4",
+3 -3
View File
@@ -33,8 +33,8 @@ WORKDIR /home/safeuser
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
# Update to the latest version of npm.
# Working with npm@7.21.1
RUN npm install -g npm@7.23.0
#RUN npm install -g npm@7.23.0
RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
RUN npm set registry http://94.130.170.209:4873/
@@ -66,7 +66,7 @@ RUN npm run docs
VOLUME /home/safeuser/keys
# Expose the port the API will be served on.
EXPOSE 5001
EXPOSE 5010
# Start the application.
COPY start-production.sh start-production.sh
+61
View File
@@ -0,0 +1,61 @@
# Docker Containers
The 'production' environment is assumed to be a set of Docker containers orchestrated with Docker Compose. The files in this directory will stand up three Docker containers:
1 An instance of go-ipfs.
2 An instance of MongoDB.
3 The JavaScript software in this repository.
The software in this repository depends on the first two containers, so if they aren't running correctly, the application won't run correctly either.
## IPFS
IPFS can be a little tricky to set up. By default, the container uses the following ports:
- 4001 for TCP connections, exposed publicly.
- 5001 for control by the application, exposed privately.
- 8080 for an IPFS gateway, consumed by the application, exposed privately.
If you already have an IPFS node running on a the computer, you will need to change the ports to avaid a conflict. To change the ports from the default, you'll need to perform a series of steps, and the order of the steps matter.
1. Edit the `docker-compose.yml` file and change the ports. Then save the file. Here is an example:
```
ports:
- 4101:4101
- 172.17.0.1:5101:5101
- 172.17.0.1:8180:8180
```
2. Bring the Docker containers up, and then back down. This will allow the IPFS container to create the config file that you'll need to edit.
- `docker-compose up -d`
- Wait a few seconds.
- `docker-compose down`
3. Update the generated config file at `../data/go-ipfs/data/config`, to update the ports in the config file, like this:
```
"Addresses": {
"API": "/ip4/0.0.0.0/tcp/5101",
"Announce": [],
"AppendAnnounce": [],
"Gateway": "/ip4/0.0.0.0/tcp/8180",
"NoAnnounce": [],
"Swarm": [
"/ip4/0.0.0.0/tcp/4101",
"/ip6/::/tcp/4101",
"/ip4/0.0.0.0/udp/4101/quic",
"/ip6/::/udp/4101/quic"
]
},
```
4. Update the port changes in the `start-production.sh` shell script. This tells the application which ports to use, in order to control the IPFS node, are are used when signaling other nodes.
5. Quickly rebuild the containers, to add the modified `start-production.sh` shell script to the application Docker container:
- `docker-compose build`
6. Now start the containers, and the port changes to IPFS should be complete.
+32 -4
View File
@@ -1,6 +1,6 @@
# Start the service with the command 'docker-compose up -d'
version: '2'
version: '3.9'
services:
mongo-ipfs-service:
@@ -13,6 +13,35 @@ services:
command: mongod --logpath=/dev/null # -- quiet
restart: always
ipfs:
image: ipfs/go-ipfs:v0.11.0
container_name: ipfs
environment:
MY_ENV_VAR: 'placeholder'
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 2000mb
ports:
- 4001:4001
- 172.17.0.1:5001:5001
- 172.17.0.1:8080:8080
volumes:
- ../data/go-ipfs/data:/data/ipfs
- ../data/go-ipfs/export:/export
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
command:
[
'daemon',
'--migrate=true',
'--agent-version-suffix=docker',
'--enable-pubsub-experiment'
]
restart: always
ipfs-service:
build: .
container_name: ipfs-service
@@ -24,10 +53,9 @@ services:
mem_limit: 500mb
links:
- mongo-ipfs-service
- ipfs
ports:
- '5001:5001' # <host port>:<container port>
- '5268:5268' # IPFS TCP
- '5269:5269' # IPFS WS
- '5010:5010' # <host port>:<container port>
volumes:
- ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
restart: always
+8 -7
View File
@@ -17,7 +17,7 @@ export COORD_NAME=ipfs-service-provider-generic
#export CR_DOMAIN=subdomain.yourdomain.com
# Debug level. 0 = minimal info. 2 = max info.
export DEBUG_LEVEL=1
export DEBUG_LEVEL=2
# END: Optional configuration settings
@@ -25,13 +25,14 @@ export DEBUG_LEVEL=1
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
# Configure IPFS ports
export IPFS_TCP_PORT=5268
export IPFS_WS_PORT=5269
# Configure REST API port
export PORT=5001
export PORT=5010
# Production settings using external go-ipfs node.
export SVC_ENV=production
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
#export IPFS_WS_PORT=5269
npm start
@@ -3,7 +3,7 @@
# This script is an example for running a generic ipfs-service-provider instance.
# Ports
export PORT=5001 # REST API port
export PORT=5005 # REST API port
export IPFS_TCP_PORT=5268
export IPFS_WS_PORT=5269
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# This script is an example for running a production environment, which is
# defined by running an external go-ipfs node.
# Ports
export PORT=5005 # REST API port
# The human-readible name that is used when displaying data about this node.
export COORD_NAME=trout-dev-bch-wallet-consumer
# This is used for end-to-end encryption (e2ee).
export MNEMONIC="stomach mean catch march census author cousin size south romance pumpkin trap"
# 0 = less verbose. 3 = most verbose
export DEBUG_LEVEL=2
# Production settings that use external IPFS node.
# https://github.com/christroutner/docker-ipfs
export SVC_ENV=production
export IPFS_HOST=localhost
export IPFS_API_PORT=5001
# Configure IPFS ports
export IPFS_TCP_PORT=4001
#export IPFS_WS_PORT=5269
# MongoDB connection string.
export DBURL=mongodb://localhost:27017/ipfs-service-dev
npm start
+2
View File
@@ -58,6 +58,8 @@ class BchAdapter {
}
}
// 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
+2
View File
@@ -17,6 +17,7 @@ const Nodemailer = require('./nodemailer')
const JSONFiles = require('./json-files')
const FullStackJWT = require('./fullstack-jwt')
const BCH = require('./bch')
const P2WDB = require('./p2wdb')
const config = require('../../config')
@@ -41,6 +42,7 @@ class Adapters {
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)
+114 -9
View File
@@ -15,8 +15,10 @@ 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.0'
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
@@ -48,12 +50,18 @@ class IpfsCoordAdapter {
this.isReady = false
// Periodically poll services for available wallet service providers.
this.pollServiceInterval = setInterval(this.pollForServices, 10000)
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
this.pollP2wdbServiceInterval = setInterval(
this.pollForP2wdbServices,
11000
)
// State object. TODO: Make this more robust.
this.state = {
serviceProviders: [],
selectedServiceProvider: ''
selectedServiceProvider: '',
p2wdbProviders: [],
selectedP2wdbProvider: ''
}
_this = this
@@ -70,6 +78,7 @@ class IpfsCoordAdapter {
circuitRelayInfo.ip4 = ip4
circuitRelayInfo.tcpPort = this.config.ipfsTcpPort
circuitRelayInfo.wsPort = this.config.ipfsWsPort
// Domain used by browser-based secure websocket connections.
circuitRelayInfo.crDomain = this.config.crDomain
@@ -78,7 +87,7 @@ class IpfsCoordAdapter {
}
}
this.ipfsCoord = new this.IpfsCoord({
const ipfsCoordOptions = {
ipfs: this.ipfs,
type: 'node.js',
// type: 'browser',
@@ -89,7 +98,14 @@ class IpfsCoordAdapter {
apiInfo: this.config.apiInfo,
announceJsonLd: this.config.announceJsonLd,
debugLevel: this.config.debugLevel
})
}
// Production env uses external go-ipfs node.
if (this.config.isProduction) {
ipfsCoordOptions.nodeType = 'external'
}
this.ipfsCoord = new this.IpfsCoord(ipfsCoordOptions)
// Wait for the ipfs-coord library to signal that it is ready.
await this.ipfsCoord.start()
@@ -112,9 +128,91 @@ class IpfsCoordAdapter {
}
}
// Poll the ipfs-coord coordination channel for available service providers.
// This method is called periodically by a timer-interval.
pollForServices () {
// 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) {
// catch and handle known failure mode.
if (
err.message.includes("Cannot read property 'peerList' of undefined")
) {
return
}
console.error('Error in pollForP2wdbServices()')
// Do not throw error. This is a top-level function.
}
}
// 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
@@ -179,7 +277,14 @@ class IpfsCoordAdapter {
}
}
} catch (err) {
console.error('Error in pollForServices(): ', err)
// catch and handle known failure mode.
if (
err.message.includes("Cannot read property 'peerList' of undefined")
) {
return
}
console.error('Error in pollForBchServices(): ', err)
// Do not throw error. This is a top-level function.
}
}
+22 -4
View File
@@ -10,7 +10,9 @@
// Global npm libraries
// const IPFS = require('ipfs')
const IPFS = require('@chris.troutner/ipfs')
// const IPFS = require('@chris.troutner/ipfs')
const IPFSembedded = require('ipfs')
const IPFSexternal = require('ipfs-http-client')
const fs = require('fs')
// Local libraries
@@ -21,11 +23,17 @@ const IPFS_DIR = './.ipfsdata/ipfs'
class IpfsAdapter {
constructor (localConfig) {
// Encapsulate dependencies
this.IPFS = IPFS
this.config = config
// Choose the IPFS constructor based on the config settings.
this.IPFS = IPFSembedded // default
if (this.config.isProduction) {
this.IPFS = IPFSexternal
}
// Properties of this class instance.
this.isReady = false
this.config = config
this.fs = fs
}
@@ -33,7 +41,7 @@ class IpfsAdapter {
async start () {
try {
// Ipfs Options
const ipfsOptions = {
const ipfsOptionsEmbedded = {
repo: IPFS_DIR,
start: true,
config: {
@@ -64,6 +72,16 @@ class IpfsAdapter {
}
}
const ipfsOptionsExternal = {
host: this.config.ipfsHost,
port: this.config.ipfsApiPort
}
let ipfsOptions = ipfsOptionsEmbedded
if (this.config.isProduction) {
ipfsOptions = ipfsOptionsExternal
}
// Create a new IPFS node.
this.ipfs = await this.IPFS.create(ipfsOptions)
+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
+13 -13
View File
@@ -7,21 +7,18 @@
// Public npm libraries.
const EventEmitter = require('events')
// Load the Clean Architecture Adapters library
// Local libraries
const config = require('../../config')
const Adapters = require('../adapters')
// Load the JSON RPC Controller.
const JSONRPC = require('./json-rpc')
// Load the Clean Architecture Use Case libraries.
const UseCases = require('../use-cases')
// const useCases = new UseCases({ adapters })
// Load the REST API Controllers.
const RESTControllers = require('./rest-api')
class Controllers {
constructor (localConfig = {}) {
// Encapsulate dependencies
this.config = config
// Initialize EventEmitters used to pass event-driven data around the app.
this.eventEmitter = new EventEmitter()
localConfig.eventEmitter = this.eventEmitter
@@ -33,13 +30,16 @@ class Controllers {
}
async attachControllers (app) {
// Wait for any startup processes to complete for the Adapters libraries.
await this.adapters.start()
// Skip in a test environment.
if (this.config.env !== 'test') {
// Wait for any startup processes to complete for the Adapters libraries.
await this.adapters.start()
// Attach the REST controllers to the Koa app.
// this.attachRESTControllers(app)
// Attach the REST controllers to the Koa app.
// this.attachRESTControllers(app)
this.attachRPCControllers()
this.attachRPCControllers()
}
}
// Top-level function for this library.
+15 -5
View File
@@ -51,6 +51,17 @@ class BchRESTControllerLib {
}
}
/**
* @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
@@ -420,10 +431,10 @@ class BchRESTControllerLib {
}
/**
* @api {JSON} /bch PubKey
* @api {REST} /bch/pubkey PubKey
* @apiPermission public
* @apiName PubKey
* @apiGroup JSON BCH
* @apiGroup REST BCH
* @apiDescription Get the public key from an address.
* Given an address the endpoint will return an object with the
* following properties
@@ -435,7 +446,7 @@ class BchRESTControllerLib {
* - publickey: - Address public key
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "pubkey", "address": "bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk"}}
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/pubkey
*
* @apiSuccessExample {json} Success-Response:
* {
@@ -452,7 +463,6 @@ class BchRESTControllerLib {
* "success": true,
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
* }
*
* }
*/
async pubKey (ctx) {
@@ -460,7 +470,7 @@ class BchRESTControllerLib {
const address = ctx.request.body.address
const data = await this.adapters.bch.getPubKey(address)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
console.log(`pubkey data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
+5
View File
@@ -13,6 +13,7 @@ const ContactRESTController = require('./contact')
const LogsRESTController = require('./logs')
const BchRESTController = require('./bch')
const IpfsRESTController = require('./ipfs')
const P2wdbRESTController = require('./p2wdb')
class RESTControllers {
constructor (localConfig = {}) {
@@ -62,6 +63,10 @@ class RESTControllers {
// 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,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
@@ -29,7 +29,8 @@ describe('#IPFS', () => {
afterEach(() => {
sandbox.restore()
clearInterval(uut.pollServiceInterval)
clearInterval(uut.pollBchServiceInterval)
clearInterval(uut.pollP2wdbServiceInterval)
})
describe('#constructor', () => {
@@ -125,7 +126,7 @@ describe('#IPFS', () => {
})
})
describe('#pollForServices', () => {
describe('#pollForBchServices', () => {
it('should find and select the wallet service', () => {
uut.ipfsCoord = {
thisNode: {
@@ -134,7 +135,7 @@ describe('#IPFS', () => {
}
}
uut.pollForServices()
uut.pollForBchServices()
// It should fine the service in the mocked data.
assert.equal(
@@ -144,7 +145,7 @@ describe('#IPFS', () => {
})
it('should catch and report errors', () => {
uut.pollForServices()
uut.pollForBchServices()
assert.isOk(true, 'Not throwing an error is a success.')
})
+1 -1
View File
@@ -46,7 +46,7 @@ const peerData = [
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'trout-bch-wallet-service-dev',
version: '1.11.1',
version: '1.11.12',
protocol: 'bch-wallet',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',