mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-21 16:52:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7e722808 | ||
|
|
03572e9c3d | ||
|
|
817b40c489 | ||
|
|
744cf60f90 | ||
|
|
f979cabd50 | ||
|
|
30942a4d6b | ||
|
|
4101615a4a | ||
|
|
34a4072384 | ||
|
|
5fc72be10c | ||
|
|
9a9f1427fd | ||
|
|
5c9539373c | ||
|
|
79484f706d | ||
|
|
7b112a13d4 | ||
|
|
6f6b5874d9 | ||
|
|
d0ef1a8669 | ||
|
|
fe5c573291 | ||
|
|
463d7df582 | ||
|
|
5a1ec640a4 | ||
|
|
8bb8dbcd23 | ||
|
|
ba212b0997 | ||
|
|
9cff05311b | ||
|
|
986d843360 | ||
|
|
4670bd22af | ||
|
|
b3e7c63e61 | ||
|
|
c352c3d6d1 | ||
|
|
5bd425a3d3 | ||
|
|
1fcc92e10d | ||
|
|
f332aece98 | ||
|
|
89f24e9940 | ||
|
|
25a75dc369 | ||
|
|
f36ce14087 | ||
|
|
df45792f47 | ||
|
|
9e71e33ccd | ||
|
|
c7b0b0c1d5 | ||
|
|
e475e5f4c3 | ||
|
|
18964d3ee4 | ||
|
|
197ece4d0d | ||
|
|
a52798b1b7 | ||
|
|
0748106637 | ||
|
|
27dcc219b9 | ||
|
|
b231686a64 | ||
|
|
60668430f5 | ||
|
|
37ab66ef23 | ||
|
|
3382e03e69 | ||
|
|
b9680508db | ||
|
|
acebfdef09 | ||
|
|
3979694109 | ||
|
|
ae888d094b | ||
|
|
e8a3234dcd | ||
|
|
fc717e0dcd | ||
|
|
17f3afbf1f | ||
|
|
18cec75f41 | ||
|
|
7621842952 | ||
|
|
1e27b477a0 | ||
|
|
08ec38e3a5 | ||
|
|
0e9a57a1da | ||
|
|
e374522725 | ||
|
|
e402f33132 | ||
|
|
e85655c0ec | ||
|
|
227ffbc4bc | ||
|
|
b41f819783 | ||
|
|
d6c3635c6a | ||
|
|
37e98e4248 | ||
|
|
6b23d00ffe | ||
|
|
53cd4650bd | ||
|
|
d734a754d6 | ||
|
|
d4863e7d7d | ||
|
|
e6941f7f7f | ||
|
|
4fdf35a55f | ||
|
|
bb44b75973 | ||
|
|
698cca555b | ||
|
|
76b3740e74 | ||
|
|
2622038df5 | ||
|
|
f1fbe34b58 | ||
|
|
d232b6404d | ||
|
|
9f92cef122 | ||
|
|
18e98f9a3b | ||
|
|
938cc755f1 | ||
|
|
6db6db8187 | ||
|
|
35af9c08fa | ||
|
|
2caf18977e | ||
|
|
8857242570 |
@@ -66,5 +66,7 @@ system-user-*.json
|
||||
orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
run-dev.sh
|
||||
data/
|
||||
|
||||
!README.md
|
||||
|
||||
@@ -8,13 +8,17 @@ When started, this web server starts an [IPFS](https://ipfs.io) node and connect
|
||||
|
||||
## Requirements
|
||||
|
||||
- node **^14.17.0**
|
||||
- npm **^7.13.0**
|
||||
- node **^14.18.2**
|
||||
- npm **^8.3.0**
|
||||
- Docker **^20.10.8**
|
||||
- Docker Compose **^1.27.4**
|
||||
|
||||
## Installation
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Note:** This software now uses an external go-ipfs IPFS node. The instructions below have not been updated to reflect this.
|
||||
|
||||
A development environment will allow you modify the code on-the-fly and contribute to the code base of this repository. [PM2](https://www.npmjs.com/package/pm2) is recommended for running this code base as an IPFS Circuit Relay.
|
||||
|
||||
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
|
||||
@@ -31,17 +35,27 @@ npm install
|
||||
|
||||
### Production Environment
|
||||
|
||||
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment. However, there is currently [a bug](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38) preventing the Docker container from being used as a Circuit Relay.
|
||||
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment.
|
||||
|
||||
```
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
- Bring the containers up once, then take them back down with `docker-compose down`.
|
||||
- Copy the [swarm.key](./swarm.key) file to `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`.
|
||||
- Bring the containers back up with `docker-compose up -d`.
|
||||
|
||||
### Operation Notes
|
||||
|
||||
- There is a memory leak in the version of js-ipfs. The app is currently configured to shut down every 8 hours to flush memory. It relies on a process manager like pm2, Docker, or systemd to restart the app after it shuts down, in order to ensure continuous operation.
|
||||
|
||||
- The PSF network operates as a private network. It does not connect or interact with the wider PSF network, relying instead on gateways to bridge the two networks, when they need to share content. This improves the performance and experience for everyone in the PSF network. To join the network, you'll need to add the [swarm.key](./swarm.key) file to the IPFS data folder.
|
||||
|
||||
- [Instructions on setting up IPFS private networks.](https://github.com/ipfs/go-ipfs/blob/master/docs/experimental-features.md#private-networks)
|
||||
- For external installations, the swarm.key file will typically go in `~/.ipfs/swarm.key`
|
||||
- For production Docker containers, the key would go in `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`
|
||||
|
||||
## Structure
|
||||
|
||||
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](troutsblog.com/blog/clean-architecture).
|
||||
|
||||
+20
-13
@@ -68,6 +68,11 @@ class Server {
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const Controllers = require('../src/controllers')
|
||||
const controllers = new Controllers()
|
||||
@@ -75,10 +80,6 @@ class Server {
|
||||
|
||||
app.controllers = controllers
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
console.log(`Running server in environment: ${config.env}`)
|
||||
@@ -91,21 +92,27 @@ class Server {
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
|
||||
// Attach the other IPFS controllers
|
||||
await controllers.attachControllers(app)
|
||||
|
||||
// ipfs-coord has a memory leak. This app shuts down after 4 hours. It
|
||||
// expects to be run by Docker or pm2, which can automatically restart
|
||||
// the app.
|
||||
setTimeout(function () {
|
||||
process.exit(0)
|
||||
}, 60000 * 60 * 2) // 2 hours
|
||||
// Attach the other IPFS controllers.
|
||||
// Skip if this is a test environment.
|
||||
if (config.env !== 'test') {
|
||||
await controllers.attachControllers(app)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Vendored
+17
-4
@@ -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
|
||||
}
|
||||
|
||||
Generated
+13030
-13937
File diff suppressed because it is too large
Load Diff
+7
-5
@@ -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",
|
||||
"axios": "0.21.1",
|
||||
"@psf/bch-js": "5.2.0",
|
||||
"axios": "0.25.0",
|
||||
"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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+31
@@ -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
|
||||
+52
-14
@@ -58,18 +58,48 @@ 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 {
|
||||
console.log(
|
||||
'this.ipfs.ipfsCoordAdapter.state: ',
|
||||
this.ipfs.ipfsCoordAdapter.state
|
||||
)
|
||||
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
const status = {
|
||||
state: this.ipfs.ipfsCoordAdapter.state
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
// 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()')
|
||||
@@ -168,8 +198,9 @@ class BchAdapter {
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data[0]
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getUtxos()')
|
||||
@@ -219,8 +250,10 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async getTransactions (address) {
|
||||
async getTransactions (address, sortOrder = 'DESCENDING', page = 0) {
|
||||
try {
|
||||
console.log('Executing getTransaction() bch adapter')
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
@@ -231,7 +264,9 @@ class BchAdapter {
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'transactions',
|
||||
addresses: [address]
|
||||
address: address,
|
||||
sortOrder,
|
||||
page
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
@@ -240,7 +275,7 @@ class BchAdapter {
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
@@ -261,7 +296,7 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async getTransaction (txid) {
|
||||
async getTransaction (txids) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
@@ -273,7 +308,7 @@ class BchAdapter {
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'transaction',
|
||||
txid
|
||||
txids
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
@@ -282,7 +317,7 @@ class BchAdapter {
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
@@ -370,6 +405,9 @@ class BchAdapter {
|
||||
dataFound = true
|
||||
// console.log('data was found in the queue')
|
||||
|
||||
// console.log(
|
||||
// `rawData.payload: ${JSON.stringify(rawData.payload, null, 2)}`
|
||||
// )
|
||||
data = rawData.payload.result.value
|
||||
|
||||
// Remove the data from the queue
|
||||
|
||||
@@ -52,12 +52,6 @@ class FullStackJWT {
|
||||
// Get's a JWT token from FullStack.cash.
|
||||
async getJWT () {
|
||||
try {
|
||||
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
|
||||
if (process.env.TEST_TYPE === 'e2e') {
|
||||
this.apiToken = 'faketoken'
|
||||
return this.apiToken
|
||||
}
|
||||
|
||||
// Log into the auth server.
|
||||
await this.jwtLib.register()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
|
||||
// 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')
|
||||
const http = require('http')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
@@ -21,11 +24,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 +42,7 @@ class IpfsAdapter {
|
||||
async start () {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptions = {
|
||||
const ipfsOptionsEmbedded = {
|
||||
repo: IPFS_DIR,
|
||||
start: true,
|
||||
config: {
|
||||
@@ -64,6 +73,17 @@ class IpfsAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
const ipfsOptionsExternal = {
|
||||
host: this.config.ipfsHost,
|
||||
port: this.config.ipfsApiPort,
|
||||
agent: http.Agent({ keepAlive: true, maxSockets: 2000 })
|
||||
}
|
||||
|
||||
let ipfsOptions = ipfsOptionsEmbedded
|
||||
if (this.config.isProduction) {
|
||||
ipfsOptions = ipfsOptionsExternal
|
||||
}
|
||||
|
||||
// Create a new IPFS node.
|
||||
this.ipfs = await this.IPFS.create(ipfsOptions)
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -308,9 +319,17 @@ class BchRESTControllerLib {
|
||||
*/
|
||||
async transactions (ctx) {
|
||||
try {
|
||||
const addr = ctx.request.body.address
|
||||
console.log('transactions REST API handler called.')
|
||||
|
||||
const data = await this.adapters.bch.getTransactions(addr)
|
||||
const addr = ctx.request.body.address
|
||||
const sortOrder = ctx.request.body.sortOrder
|
||||
const page = ctx.request.body.page
|
||||
|
||||
const data = await this.adapters.bch.getTransactions(
|
||||
addr,
|
||||
sortOrder,
|
||||
page
|
||||
)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
@@ -323,8 +342,8 @@ class BchRESTControllerLib {
|
||||
* @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
|
||||
* @apiDescription Get data about specific transactions.
|
||||
* Given an array of transaction IDs this endpoint will return an array of objects
|
||||
* with the following properties
|
||||
*
|
||||
* - txid: "" - Transaction ID
|
||||
@@ -342,11 +361,11 @@ class BchRESTControllerLib {
|
||||
* - isValidSLPTx: - Determines if the transaction was under SLP
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/transaction
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txids": ["01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"] }' localhost:5001/bch/transaction
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* [{
|
||||
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "version":1,
|
||||
@@ -395,7 +414,7 @@ class BchRESTControllerLib {
|
||||
* "time":1568338904,
|
||||
* "blocktime":1568338904,
|
||||
* "isValidSLPTx":false
|
||||
* }
|
||||
* }]
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
@@ -408,9 +427,9 @@ class BchRESTControllerLib {
|
||||
*/
|
||||
async transaction (ctx) {
|
||||
try {
|
||||
const txid = ctx.request.body.txid
|
||||
const txids = ctx.request.body.txids
|
||||
|
||||
const data = await this.adapters.bch.getTransaction(txid)
|
||||
const data = await this.adapters.bch.getTransaction(txids)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
@@ -420,10 +439,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 +454,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 +471,6 @@ class BchRESTControllerLib {
|
||||
* "success": true,
|
||||
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
async pubKey (ctx) {
|
||||
@@ -460,7 +478,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) {
|
||||
|
||||
@@ -86,6 +86,7 @@ class BchRouter {
|
||||
}
|
||||
|
||||
async postTransactions (ctx, next) {
|
||||
console.log('Calling _this.bchRESTController.transactions()')
|
||||
await _this.bchRESTController.transactions(ctx, next)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
/key/swarm/psk/1.0.0/
|
||||
/base16/
|
||||
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
|
||||
+98
-39
@@ -7,7 +7,7 @@
|
||||
This test will query each REST API endpoint and ensure they are working correctly.
|
||||
*/
|
||||
|
||||
const SERVER = 'http://localhost:5001'
|
||||
const SERVER = 'http://localhost:5005'
|
||||
|
||||
const axios = require('axios')
|
||||
const assert = require('chai').assert
|
||||
@@ -23,8 +23,8 @@ describe('#BCH E2E Tests', () => {
|
||||
const result = await axios.get(url)
|
||||
// console.log(`data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.isAbove(result.data.status.state.serviceProviders.length, 0)
|
||||
assert.isOk(result.data.status.state.selectedServiceProvider)
|
||||
assert.isAbove(result.data.status.serviceProviders.length, 0)
|
||||
assert.isOk(result.data.status.selectedProvider)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -56,16 +56,16 @@ describe('#BCH E2E Tests', () => {
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.address, body.address)
|
||||
assert.property(result.data, 'bchUtxos')
|
||||
assert.property(result.data, 'slpUtxos')
|
||||
assert.equal(result.data[0].address, body.address)
|
||||
assert.property(result.data[0], 'bchUtxos')
|
||||
assert.property(result.data[0], 'slpUtxos')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should attempt to broadcast a transaction', async () => {
|
||||
const body = {
|
||||
hex: "'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'"
|
||||
hex: '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/broadcast`
|
||||
@@ -74,42 +74,11 @@ describe('#BCH E2E Tests', () => {
|
||||
|
||||
assert.equal(result.data.status, 422)
|
||||
assert.equal(result.data.success, false)
|
||||
assert.equal(result.data.message, 'TX decode failed')
|
||||
assert.equal(result.data.message, 'Missing inputs')
|
||||
assert.equal(result.data.endpoint, 'broadcast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transactions', () => {
|
||||
it('should get transaction history for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.property(result.data, 'transactions')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
const body = {
|
||||
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transaction`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.property(result.data, 'txid')
|
||||
assert.property(result.data, 'vin')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pubkey', () => {
|
||||
it('should return "not found" if pubkey is not on chain', async () => {
|
||||
const body = {
|
||||
@@ -146,4 +115,94 @@ describe('#BCH E2E Tests', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transactions', () => {
|
||||
it('should get transaction history for an address, and sort by default in descending order.', async () => {
|
||||
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
|
||||
const body = {
|
||||
address
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert expected values and properties.
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.address, address)
|
||||
assert.isArray(result.data.txs)
|
||||
|
||||
// Assert expected sorting
|
||||
assert.isAbove(result.data.txs[0].height, result.data.txs[1].height)
|
||||
})
|
||||
|
||||
it('should sort in ascending order.', async () => {
|
||||
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
|
||||
|
||||
const body = {
|
||||
address,
|
||||
sortOrder: 'ASCENDING'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert expected values and properties.
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.address, address)
|
||||
assert.isArray(result.data.txs)
|
||||
|
||||
// Assert expected sorting
|
||||
assert.isAbove(result.data.txs[1].height, result.data.txs[0].height)
|
||||
})
|
||||
|
||||
it('should paginate an address with deep tx history', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert there is no more than 100 entries
|
||||
assert.isBelow(result.data.txs.length, 101)
|
||||
})
|
||||
|
||||
it('should get the second page of results', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d',
|
||||
page: 1
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert there is no more than 100 entries
|
||||
assert.isBelow(result.data.txs.length, 101)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
const body = {
|
||||
txids: [
|
||||
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
]
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transaction`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.isArray(result.data.txData)
|
||||
assert.property(result.data.txData[0].txData, 'txid')
|
||||
assert.property(result.data.txData[0].txData, 'vin')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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.')
|
||||
})
|
||||
|
||||
@@ -12,9 +12,15 @@ const ipfs = {
|
||||
peer: {
|
||||
sendPrivateMessage: () => {}
|
||||
}
|
||||
},
|
||||
thisNode: {
|
||||
peerData: []
|
||||
}
|
||||
},
|
||||
peerInputHandler: () => {}
|
||||
peerInputHandler: () => {},
|
||||
state: {
|
||||
serviceProviders: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.',
|
||||
|
||||
Reference in New Issue
Block a user