Compare commits

...
228 Commits
Author SHA1 Message Date
Chris Troutner 9e14e24eb4 Merge pull request #44 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2023-04-27 19:17:31 -07:00
Chris Troutner 1eb68c9ea5 fix(deps): Updating dependencies 2023-04-06 06:33:23 -07:00
Chris Troutner c26ad11290 adding curl example for refreshing token cache 2023-04-06 04:55:59 -07:00
Chris Troutner 00a7b78fde Removing debug statements 2023-02-13 07:31:19 -08:00
Chris Troutner 9d80341357 debugging preferred provider 2023-02-13 07:22:29 -08:00
Chris Troutner 00b76226ec Merge pull request #43 from Permissionless-Software-Foundation/ct-unstable
fixing improper array
2023-02-13 06:56:46 -08:00
Chris Troutner f0552bce32 fix(utxos): Fixing typo 2023-02-13 06:55:37 -08:00
Chris Troutner 4aa7de253d fixing improper array 2023-02-08 15:06:07 -08:00
Chris Troutner abd01e647a Merge pull request #42 from Permissionless-Software-Foundation/ct-unstable
feat(utxoBulk): Adding REST API endpoint for UTXO bulk queries
2023-02-08 12:45:55 -08:00
Chris Troutner 8237fec8b7 Updating package-lock 2023-02-08 12:40:03 -08:00
Chris Troutner 0d06b2eaaa feat(utxoBulk): Adding REST API endpoint for UTXO bulk queries 2023-02-08 12:17:01 -08:00
Chris Troutner 82ea90c45c fix(getTokenData2()): Writing value of updateCache to console 2023-01-27 07:27:17 -08:00
Chris Troutner fbe7bf9742 Merge pull request #41 from Permissionless-Software-Foundation/ct-unstable
fix(getTokenData2()): Adding argument for updateCache
2023-01-26 18:09:53 -08:00
Chris Troutner 4447c2224e fix(getTokenData2()): Adding argument for updateCache 2023-01-26 18:08:51 -08:00
Chris Troutner db72e0c95e Merge pull request #40 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v6.5.3
2022-11-02 05:01:33 -07:00
Chris Troutner 9c9c3e7770 fix(bch-js): Updating to v6.5.3 2022-11-02 04:59:33 -07:00
Chris Troutner 2ef0130559 Merge pull request #39 from Permissionless-Software-Foundation/ct-unstable
Locking perferred provider set in env var
2022-10-25 15:41:35 -07:00
Chris Troutner 610ca4ec8b linting 2022-10-25 15:38:17 -07:00
Chris Troutner 1a32b941df fix(preferred provider): Throwing error if provider set in env var and clinet tries to change it 2022-10-25 15:38:07 -07:00
Chris Troutner 433931111c Merge pull request #38 from Permissionless-Software-Foundation/ct-unstable
feat(XEC price): Adding endpoint to retrieve XEC price
2022-10-17 10:20:20 -07:00
Chris Troutner 3edc945487 feat(XEC price): Adding endpoint to retrieve XEC price 2022-10-17 10:18:52 -07:00
Chris Troutner c66194ceeb Merge pull request #37 from Permissionless-Software-Foundation/ct-unstable
fix(getTokenData2): Adding to REST API
2022-10-07 06:32:36 -07:00
Chris Troutner e35e130816 fix(getTokenData2): Adding to REST API 2022-10-07 06:30:55 -07:00
Chris Troutner bba4bd5931 Merge pull request #36 from Permissionless-Software-Foundation/ct-unstable
feat(getTokenData2): Adding token icon and media retrieval
2022-10-07 06:02:43 -07:00
Chris Troutner 48d13ed6bc feat(getTokenData2): Adding token icon and media retrieval 2022-10-07 06:01:23 -07:00
Chris Troutner d6f9b1b754 fix(documentation): slight improvement to docs 2022-08-13 09:38:55 -07:00
Chris Troutner 25edd5fe71 Merge pull request #35 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-08-03 18:59:42 -07:00
Chris Troutner cc84636e54 fix(mocks): Fixing bad syntax in a mock file 2022-08-03 18:57:20 -07:00
Chris Troutner d73211191c fix(upstream): Syncin with upstream ipfs-service-provider 2022-08-03 17:49:59 -07:00
Chris Troutner b23acd3cdc Merge pull request #97 from Permissionless-Software-Foundation/ct-unstable
fix(production): Switching to Docker Image
2022-07-25 19:17:27 -07:00
Chris Troutner c71fda38ff fix(production): Switching to Docker Image 2022-07-25 19:14:44 -07:00
Chris Troutner f99f49d840 Merge pull request #96 from Permissionless-Software-Foundation/ct-unstable
Configuring ports with env vars
2022-07-25 17:58:40 -07:00
Chris Troutner 2edd924c57 fix(production): Adding 4003 websocket port 2022-07-25 17:56:02 -07:00
Chris Troutner 689c8fd8d9 fix(ipfs): Using config to set ports 2022-07-25 17:40:05 -07:00
Chris Troutner ffd6c452c9 Merge pull request #95 from Permissionless-Software-Foundation/ct-unstable
fix(mocks): Another update to IPFS test mocks
2022-07-24 08:15:55 -07:00
Chris Troutner fc8e160874 fix(mocks): Another update to IPFS test mocks 2022-07-24 08:14:27 -07:00
Chris Troutner d2f83307e6 Merge pull request #94 from Permissionless-Software-Foundation/ct-unstable
fix(mocks): Improvement to adapter mocks used in tests
2022-07-24 08:08:13 -07:00
Chris Troutner 1a09fcd26f fix(mocks): Improvement to adapter mocks used in tests 2022-07-24 08:06:32 -07:00
Chris Troutner e96129d7b0 Merge pull request #34 from Permissionless-Software-Foundation/ct-unstable
fix(getTokenData): Adding flag to retrieve token TX history
2022-07-12 16:05:48 -07:00
Chris Troutner 4d852bb2ee fix(getTokenData): Adding flag to retrieve token TX history 2022-07-12 16:03:51 -07:00
Chris Troutner cbefd5e8af fix(getTokenData): passing flag for tx history 2022-07-12 15:31:18 -07:00
Chris Troutner bb5ea4a93d Merge pull request #33 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-07-08 09:41:43 -07:00
Chris Troutner 6e7396ba07 Fixing merge conflicts with upstream ipfs-service-provider 2022-07-08 09:40:07 -07:00
Chris Troutner e2c5821dbe Merge pull request #93 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating bootstrap nodes
2022-07-07 15:56:42 -07:00
Chris Troutner 9967d5145b fix(ipfs-coord): Updating bootstrap nodes 2022-07-07 15:55:33 -07:00
Chris Troutner 9d16291855 Merge pull request #92 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v8
2022-07-07 15:17:09 -07:00
Chris Troutner 1518d1a12b fix(ipfs-coord): Updating to v8 2022-07-07 15:16:03 -07:00
Chris Troutner b68a143071 Merge pull request #91 from Permissionless-Software-Foundation/ct-unstable
Added hooks for async startup
2022-06-27 11:33:50 -07:00
Chris Troutner 5c25ebeeca Renaming mock log 2022-06-27 11:32:25 -07:00
Chris Troutner 3b17ba8e8b fix(logapi): Fixing issues with unit tests 2022-06-27 11:29:27 -07:00
Chris Troutner 434a4d7bd6 Refactoring failing unit tests 2022-06-27 10:25:19 -07:00
Chris Troutner dd36ebc7a0 Got test coverage back to 100% 2022-06-27 10:15:35 -07:00
Chris Troutner f117fe15df fix(startup): Added hooks for async startup 2022-06-27 10:09:08 -07:00
Chris Troutner 3a45c96467 Merge pull request #32 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updating to docker container v1.8.4
2022-06-21 17:21:25 -07:00
Chris Troutner 9ca2c1b9db fix(production): Updating to docker container v1.8.4 2022-06-21 17:18:41 -07:00
Chris Troutner 7b9bde456e Merge pull request #31 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-06-21 15:59:09 -07:00
Chris Troutner ed0d468c2d Fixing merge conflicts 2022-06-21 15:56:38 -07:00
Chris Troutner fe1b428dac Adding production env var 2022-06-21 15:34:31 -07:00
Chris Troutner 0333f77f5a Fixing broken link in README 2022-06-20 07:30:10 -07:00
Chris Troutner 3c908d1a4d Merge pull request #90 from Permissionless-Software-Foundation/ct-unstable
Increasing test coverage to 100%
2022-06-20 07:22:10 -07:00
Chris Troutner eb46a4c130 fix(tests): Got to 100% test coverage for entire repo 2022-06-20 07:19:36 -07:00
Chris Troutner d0efdcfa07 fix(ipfs-coord): Increased unit test coverage to 100% 2022-06-20 07:01:43 -07:00
Chris Troutner 62a7fcb16d fix(tests): Got bin/server.js unit test coverage to 100% 2022-06-19 20:21:40 -07:00
Chris Troutner d8addd5b27 Trying to build unit tests for bin/server 2022-06-19 20:00:25 -07:00
Chris Troutner bf6f1cf62d Merge pull request #89 from Permissionless-Software-Foundation/ct-unstable
Updating dependencies
2022-06-19 13:25:07 -07:00
Chris Troutner b7df7c47a5 Updated ipfs 2022-06-19 13:23:52 -07:00
Chris Troutner ae260db4af Updated some dependencies 2022-06-19 13:19:31 -07:00
Chris Troutner 778098ca12 fix(ipfs-coord): Updating to v7.1.19 2022-05-28 09:29:31 -07:00
Chris Troutner 0c4bb9c4a1 fix(ipfs-coord): Updating to v7.1.18 2022-05-28 09:09:44 -07:00
Chris Troutner 9cb975656d Merge pull request #88 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.17
2022-05-28 08:00:57 -07:00
Chris Troutner 4f6564423e fix(ipfs-coord): Updating to v7.1.17 2022-05-28 07:59:47 -07:00
Chris Troutner 54e7e74ae5 Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
Subscribing to chat channel at startup
2022-05-26 09:14:50 -07:00
Chris Troutner 324c372119 fix(startup): Subscribing to chat pubsub channel at startup 2022-05-26 09:12:15 -07:00
Chris Troutner da62f1bbe3 linting 2022-05-26 08:24:45 -07:00
Chris Troutner 129047fe88 fix(startup): Announcing config settings at startup 2022-05-26 08:24:30 -07:00
Chris Troutner 3f652688b0 Merge pull request #30 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Using updated image
2022-05-16 15:53:39 -07:00
Chris Troutner 3ccbfdb614 fix(docker): Using updated image 2022-05-16 15:52:34 -07:00
Chris Troutner 5b4d00b949 Merge pull request #29 from Permissionless-Software-Foundation/ct-unstable
fix(docker): mounting startup script on startup
2022-05-16 15:18:42 -07:00
Chris Troutner a666dbcafd fix(docker): mounting startup script on startup 2022-05-16 15:16:48 -07:00
Chris Troutner 3d6d544c21 Merge pull request #28 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Using image rather than dockerfile
2022-05-16 14:51:22 -07:00
Chris Troutner 2ca54865bc fix(docker): Using image rather than dockerfile 2022-05-16 14:18:09 -07:00
Chris Troutner df778d6d8e Merge pull request #27 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider & removing event emitter dependency.
2022-05-16 14:08:55 -07:00
Chris Troutner 3c64f44dfd Reducing screen spam 2022-05-16 14:06:37 -07:00
Chris Troutner 45ea47d4d0 fix(config): Fixing env var setting 2022-05-16 13:56:19 -07:00
Chris Troutner 150e3d6799 Fixing env var 2022-05-16 13:54:04 -07:00
Chris Troutner b3011bcb69 linting 2022-05-16 13:32:46 -07:00
Chris Troutner ccbe9a6707 feat(event emitter): Removing dependency on event emitter 2022-05-16 13:32:16 -07:00
Chris Troutner 23ebdaa7f3 Reducing debug level 2022-05-16 12:35:51 -07:00
Chris Troutner fb182ced58 Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
Updating docker-compose file
2022-05-16 12:33:03 -07:00
Chris Troutner bec7c11e46 Updating README 2022-05-16 12:31:53 -07:00
Chris Troutner fb10e10226 fix(production): Updating docker images 2022-05-16 12:30:58 -07:00
Chris Troutner 5b37e47747 Merge pull request #85 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v6.4.1
2022-05-16 12:15:47 -07:00
Chris Troutner a3cea782ba fix(bch-js): Updating to v6.4.1 2022-05-16 12:14:20 -07:00
Chris Troutner 14ffdae194 Merge pull request #84 from Permissionless-Software-Foundation/dh-test-coverage
feat(coverage): Got ipfs-coord.js to 100% test coverage
2022-05-16 12:09:18 -07:00
Chris Troutner 5792871e0a Merge pull request #26 from Permissionless-Software-Foundation/ct-unstable
Adding utxoIsValid and getTokenData endpoints
2022-05-10 13:19:27 -07:00
Chris Troutner c2ec08f03d feat(getTokenData): Added endpoint for getting data associated with a token 2022-05-10 13:18:24 -07:00
Chris Troutner e4489cfcac linting 2022-05-10 13:06:21 -07:00
Chris Troutner e8c08beeb3 feat(utxoIsValid): Adding endpoint support for utxoIsValid 2022-05-10 13:06:03 -07:00
Chris Troutner 4a64423885 fix(bch-js): Updated to v6.3.4 2022-05-10 11:53:55 -07:00
Daniel Gonzalez 7e3ce1564d feat(coverage): Got ipfs-coord.js to 100% test coverage 2022-03-25 22:12:11 -04:00
Chris Troutner 8154dd96f9 Merge pull request #83 from Permissionless-Software-Foundation/ct-unstable
fix(deps): Updating bch-js
2022-03-20 07:48:42 -07:00
Chris Troutner e37223cc88 fix(deps): Updating bch-js 2022-03-20 07:46:08 -07:00
Chris Troutner 5002ef2b90 Merge pull request #82 from Permissionless-Software-Foundation/dh-test-coverage-ipfsjs
feat(test): Got ipfs.js to 100% test coverage
2022-03-18 11:27:32 -07:00
Chris Troutner 5d95109ad4 more linting 2022-03-18 11:24:15 -07:00
Chris Troutner 13b3150f62 minor linting 2022-03-18 11:24:04 -07:00
Daniel Gonzalez b3a4a7c1ab feat(test): Got ipfs.js to 100% test coverage 2022-03-15 18:58:55 -04:00
Chris Troutner d6b64fce59 Merge pull request #25 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updating IPFS container from v11 to v12
2022-03-15 14:15:22 -07:00
Chris Troutner 8c12222d9c fix(production): Updating IPFS container from v11 to v12 2022-03-15 14:13:22 -07:00
Chris Troutner 26a79d1880 Merge pull request #24 from Permissionless-Software-Foundation/ct-unstable
fix(production): Slight tweeks to docker container
2022-03-15 13:33:12 -07:00
Chris Troutner 3f96b608b5 fix(production): Slight tweeks to docker container 2022-03-15 13:31:00 -07:00
Chris Troutner a669fc6440 Merge pull request #23 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.5
2022-03-10 09:58:44 -08:00
Chris Troutner 42ee03a6d9 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-10 09:56:50 -08:00
Chris Troutner 733e8fd405 Merge pull request #81 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.6
2022-03-10 09:29:32 -08:00
Chris Troutner baee72c1c6 fix(ipfs-coord): Updating to v7.1.6 2022-03-10 09:27:13 -08:00
Chris Troutner 5c2ab8a8cf Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-10 08:47:54 -08:00
Chris Troutner 11618a796f Merge pull request #80 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.5
2022-03-10 08:35:21 -08:00
Chris Troutner 025ae20704 fix(ipfs-coord): Updating to v7.1.5 2022-03-10 08:33:53 -08:00
Chris Troutner 5f6b98a1e5 Merge pull request #22 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-03-08 09:58:21 -08:00
Chris Troutner e1f5e3fa9c Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-08 09:54:23 -08:00
Chris Troutner fdf73610eb Merge pull request #79 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Updating tests for node.js v16
2022-03-08 09:42:42 -08:00
Chris Troutner b09e77c079 fix(tests): Updating tests for node.js v16 2022-03-08 09:39:54 -08:00
Chris Troutner d583acd6f3 Merge pull request #21 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.4
2022-03-05 14:53:33 -08:00
Chris Troutner 079bd1bebc Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-03-05 14:48:35 -08:00
Chris Troutner 68ad2561b1 Merge pull request #78 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.4
2022-03-05 14:22:25 -08:00
Chris Troutner 791d11e597 Merge branch 'master' into ct-unstable 2022-03-05 14:20:19 -08:00
Chris Troutner 5489e76f55 fix(ipfs-coord): Updating to v7.1.4 2022-03-05 14:20:13 -08:00
Chris Troutner a205f7967f lint 2022-03-05 09:30:36 -08:00
Chris Troutner 346ba79929 Removing redundent debug log 2022-03-05 09:30:25 -08:00
Chris Troutner 14a9917670 Merge pull request #20 from Permissionless-Software-Foundation/ct-upstream
fix(ipfs-coord): Updating to v7.1.3
2022-03-05 09:21:16 -08:00
Chris Troutner e4fe851871 fix(upstream): Merging with upstream. Updating ipfs-coord 2022-03-05 09:19:35 -08:00
Chris Troutner 496a1dd07c Merge pull request #77 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Updating to v7.1.3
2022-03-05 08:26:32 -08:00
Chris Troutner a7b8ca9ad5 linting 2022-03-05 08:24:50 -08:00
Chris Troutner 6eb16eb11a fix(ipfs-coord): Updating to v7.1.3 2022-03-05 08:24:36 -08:00
Chris Troutner 589b723629 Increasing sockets to 200 2022-02-24 17:56:41 -08:00
Chris Troutner 5ec45ee9cb fix(ipfs): Reducing sockets from 2000 to 20 2022-02-24 17:35:34 -08:00
Chris Troutner 915d3563c2 Merge pull request #19 from Permissionless-Software-Foundation/ct-unstable
Adding /price/usd REST endpoint
2022-02-02 15:57:05 -08:00
Chris Troutner 9446efe1ae feat(price): Adding /price/usd endpoint 2022-02-02 15:55:31 -08:00
Chris Troutner c573b71a23 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-01-31 19:52:43 -08:00
Chris Troutner 1844f30dc5 Merge pull request #76 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord): Bumping to v6.8.12
2022-01-31 10:31:20 -08:00
Chris Troutner b6ab2f02f4 Updating package-lock 2022-01-31 10:29:45 -08:00
Chris Troutner b5c76aa5fe fix(ipfs-coord): Bumping to v6.8.12 2022-01-31 10:28:06 -08:00
Chris Troutner 26c0611c51 Merge pull request #17 from Permissionless-Software-Foundation/ct-unstable
Renaming transaction and transactions to txData and txHistory
2022-01-29 09:49:18 -08:00
Chris Troutner 3345160447 fixed unit tests 2022-01-29 09:47:19 -08:00
Chris Troutner 8d4984c4ac fix(txData): Renamed transaction REST API endpoint to txData 2022-01-29 09:31:59 -08:00
Chris Troutner b2cc85df86 feat(txHistory): Renamed transactions endpoint to txHistory 2022-01-29 08:28:13 -08:00
Chris Troutner 51977adad8 fix(production): Adding .env file to supress docker orphan warnings 2022-01-28 20:09:56 -08:00
Chris Troutner 5c7e722808 Merge pull request #16 from Permissionless-Software-Foundation/ct-unstable
Refactored transaction and transactions endpoints
2022-01-28 18:54:43 -08:00
Chris Troutner 03572e9c3d Restoring e2e tests 2022-01-28 15:50:25 -08:00
Chris Troutner 817b40c489 fix(transaction): Refactored transaction 2022-01-28 15:49:54 -08:00
Chris Troutner 744cf60f90 Replacing previous versions of transactions() with new version 2022-01-28 06:25:29 -08:00
Chris Troutner f979cabd50 Merge pull request #15 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2022-01-20 09:17:03 -08:00
Chris Troutner 30942a4d6b Fixing merge conflicts 2022-01-20 09:14:37 -08:00
Chris Troutner 4101615a4a Merge pull request #75 from Permissionless-Software-Foundation/ct-unstable
Maintenance
2022-01-20 08:05:50 -08:00
Chris Troutner 34a4072384 fix(restart): Removing code the auto-restarts app after 2 hours 2022-01-20 07:56:40 -08:00
Chris Troutner 5fc72be10c fix(fullstack-jwt.js): Got unit test coverage back to 100% 2022-01-20 07:53:12 -08:00
Chris Troutner 9a9f1427fd Merge pull request #14 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v5
2022-01-18 15:43:56 -08:00
Chris Troutner 5c9539373c Merge branch 'master' into ct-unstable 2022-01-18 15:41:08 -08:00
Chris Troutner 79484f706d fix(bch-js): Updating to v5 2022-01-18 15:40:21 -08:00
Chris Troutner 7b112a13d4 fix(cors): Fixing bug leading to CORS error 2022-01-12 15:33:04 -08:00
Chris Troutner 6f6b5874d9 Merge pull request #13 from Permissionless-Software-Foundation/ct-unstable
fix(cors): slight tweak to avoid CORS error
2022-01-12 14:50:28 -08:00
Chris Troutner d0ef1a8669 fix(cors): slight tweak to avoid CORS error 2022-01-12 14:48:33 -08:00
Chris Troutner fe5c573291 Merge pull request #74 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Skipping IPFS stuff in test environment
2022-01-09 11:54:41 -08:00
Chris Troutner 463d7df582 fix(tests): Skipping IPFS stuff in test environment 2022-01-09 11:51:15 -08:00
Chris Troutner 5a1ec640a4 Merge pull request #73 from Permissionless-Software-Foundation/ct-unstable
Adding swarm.key
2022-01-03 11:39:57 -08:00
Chris Troutner 8bb8dbcd23 Updating READMe 2022-01-03 11:35:26 -08:00
Chris Troutner ba212b0997 fix(ipfs): Adding swarm.key file for joining network 2022-01-03 11:22:15 -08:00
Chris Troutner 9cff05311b Merge pull request #12 from Permissionless-Software-Foundation/ct-unstable
Syncing upstream ipfs-service-provider
2022-01-02 19:19:21 -08:00
Chris Troutner 986d843360 Merge branch 'master' of https://github.com/Permissionless-Software-Foundation/ipfs-service-provider into ct-unstable 2022-01-02 19:17:11 -08:00
Chris Troutner 4670bd22af Merge pull request #72 from Permissionless-Software-Foundation/ct-unstable
fix(pubsub): Applying fix for pubsub in external node
2022-01-02 19:13:44 -08:00
Chris Troutner b3e7c63e61 fix(pubsub): Applying fix for pubsub in external node 2022-01-02 19:00:50 -08:00
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
Chris Troutner 2caf18977e Merge pull request #6 from Permissionless-Software-Foundation/ct-unstable
fix(bch/getStats()): Adding names to service provider IDs
2021-12-17 15:13:48 -08:00
Chris Troutner 8857242570 fix(bch/getStats()): Adding names to service provider IDs 2021-12-14 17:04:55 -08:00
53 changed files with 16703 additions and 22150 deletions
+2
View File
@@ -66,5 +66,7 @@ system-user-*.json
orbitdb
ipfsdata
.ipfsdata
run-dev.sh
data/
!README.md
+1 -1
View File
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2021 Permissionless Software Foundation
Copyright (c) 2022 Permissionless Software Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+18 -6
View File
@@ -8,13 +8,17 @@ When started, this web server starts an [IPFS](https://ipfs.io) node and connect
## Requirements
- node **^14.17.0**
- npm **^7.13.0**
- node **^14.18.2**
- npm **^8.3.0**
- Docker **^20.10.8**
- Docker Compose **^1.27.4**
## Installation
### Development Environment
**Note:** This software now uses an external go-ipfs IPFS node. The instructions below have not been updated to reflect this.
A development environment will allow you modify the code on-the-fly and contribute to the code base of this repository. [PM2](https://www.npmjs.com/package/pm2) is recommended for running this code base as an IPFS Circuit Relay.
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
@@ -31,17 +35,25 @@ npm install
### Production Environment
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment. However, there is currently [a bug](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38) preventing the Docker container from being used as a Circuit Relay.
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment.
```
docker-compose build --no-cache
docker-compose pull
docker-compose up -d
```
You can bring the containers back up with `docker-compose up -d`.
### Operation Notes
- There is a memory leak in the version of js-ipfs. The app is currently configured to shut down every 8 hours to flush memory. It relies on a process manager like pm2, Docker, or systemd to restart the app after it shuts down, in order to ensure continuous operation.
- The PSF network operates as a private network. It does not connect or interact with the wider PSF network, relying instead on gateways to bridge the two networks, when they need to share content. This improves the performance and experience for everyone in the PSF network. To join the network, you'll need to add the [swarm.key](./swarm.key) file to the IPFS data folder.
- [Instructions on setting up IPFS private networks.](https://github.com/ipfs/go-ipfs/blob/master/docs/experimental-features.md#private-networks)
- For external installations, the swarm.key file will typically go in `~/.ipfs/swarm.key`
- For production Docker containers, the key would go in `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`
## Structure
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](troutsblog.com/blog/clean-architecture).
@@ -60,6 +72,8 @@ API documentation is written inline and generated by [apidoc](http://apidocjs.co
Visit `http://localhost:5000/docs/` to view docs
There is additional developer documentation in the [dev-docs directory](./dev-docs).
## Dependencies
- [koa2](https://github.com/koajs/koa/tree/v2.x)
@@ -83,5 +97,3 @@ Snapshots pinned to IPFS will be listed here.
## License
[MIT](./LICENSE.md)
test
+49 -26
View File
@@ -24,31 +24,37 @@ const config = require('../config') // this first.
const AdminLib = require('../src/adapters/admin')
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
const { wlogger } = require('../src/adapters/wlogger')
const Controllers = require('../src/controllers')
class Server {
constructor () {
// Encapsulate dependencies
this.adminLib = new AdminLib()
this.controllers = new Controllers()
this.mongoose = mongoose
this.config = config
this.process = process
}
async startServer () {
try {
// Create a Koa instance.
const app = new Koa()
app.keys = [config.session]
app.keys = [this.config.session]
// Connect to the Mongo Database.
mongoose.Promise = global.Promise
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
this.mongoose.Promise = global.Promise
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${config.database}`
`Connecting to MongoDB with this connection string: ${this.config.database}`
)
await mongoose.connect(config.database, {
await this.mongoose.connect(this.config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
})
console.log(`Starting environment: ${config.env}`)
console.log(`Debug level: ${config.debugLevel}`)
console.log(`Starting environment: ${this.config.env}`)
console.log(`Debug level: ${this.config.debugLevel}`)
// MIDDLEWARE START
@@ -68,44 +74,61 @@ class Server {
app.use(passport.initialize())
app.use(passport.session())
// Attach REST API and JSON RPC controllers to the app.
const Controllers = require('../src/controllers')
const controllers = new Controllers()
await controllers.attachRESTControllers(app)
app.controllers = controllers
// 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: '*' }))
// Wait for any adapters to initialize.
await this.controllers.initAdapters()
// Wait for any use-libraries to initialize.
await this.controllers.initUseCases()
// Attach REST API and JSON RPC controllers to the app.
await this.controllers.attachRESTControllers(app)
app.controllers = this.controllers
// MIDDLEWARE END
console.log(`Running server in environment: ${config.env}`)
wlogger.info(`Running server in environment: ${config.env}`)
console.log(`Running server in environment: ${this.config.env}`)
wlogger.info(`Running server in environment: ${this.config.env}`)
await app.listen(config.port)
console.log(`Server started on ${config.port}`)
this.server = await app.listen(this.config.port)
console.log(`Server started on ${this.config.port}`)
// Create the system admin user.
const success = await this.adminLib.createSystemUser()
if (success) console.log('System admin user created.')
// Attach the other IPFS controllers
await controllers.attachControllers(app)
// Attach the other IPFS controllers.
// Skip if this is a test environment.
if (this.config.env !== 'test') {
await this.controllers.attachControllers(app)
}
// ipfs-coord has a memory leak. This app shuts down after 4 hours. It
// expects to be run by Docker or pm2, which can automatically restart
// the app.
setTimeout(function () {
process.exit(0)
}, 60000 * 60 * 2) // 2 hours
// Display configuration settings
console.log('\nConfiguration:')
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
return app
} catch (err) {
console.error('Could not start server. Error: ', err)
console.log(
'Exiting after 5 seconds. Depending on process manager to restart.'
)
await this.sleep(5000)
this.process.exit(1)
}
}
sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
}
module.exports = Server
+19 -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,33 @@ 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.CONSUMER_ENV === 'production' ? true : false,
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
ipfsApiPort: process.env.IPFS_API_PORT
? parseInt(process.env.IPFS_API_PORT)
: 5001,
chatPubSubChan: 'psf-ipfs-chat-001'
}
+2
View File
@@ -7,3 +7,5 @@ This directory contains documentation for developers.
This app provides a bridge between the Web 2.0 REST API world and the Web3.0 IPFS world. Web apps (like wallet.fullstack.cash) can interact with the REST API provided by the this web server app. This app then pulls the back-end infrastructure through a the IPFS network.
When started, this app will spin up an IPFS node and connect to the PSF network using [ipfs-coord](https://www.npmjs.com/package/ipfs-coord). It will find and connect with a BCH wallet service provider via IPFS. Clients connecting to the REST API can then access BCH wallet infrastructure via the REST API.
More details at [CashStack.info](https://cashstack.info).
+14058 -21749
View File
File diff suppressed because it is too large Load Diff
+13 -11
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",
"axios": "0.21.1",
"@psf/bch-js": "6.7.1",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs-coord": "6.7.4",
"ipfs": "0.62.3",
"ipfs-coord": "8.0.8",
"ipfs-http-client": "55.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -42,19 +43,19 @@
"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",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.4.17",
"nodemailer": "6.7.5",
"passport-local": "1.0.0",
"public-ip": "4.0.4",
"semver": "^7.3.5",
"semver": "7.3.5",
"winston": "3.3.3",
"winston-daily-rotate-file": "4.5.0"
},
"devDependencies": {
"apidoc": "0.26.0",
"apidoc": "0.51.1",
"chai": "4.3.0",
"coveralls": "3.1.0",
"eslint": "7.19.0",
@@ -64,9 +65,10 @@
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "4.3.8",
"mocha": "8.2.1",
"lodash.clonedeep": "4.5.0",
"mocha": "10.0.0",
"nyc": "15.1.0",
"semantic-release": "17.4.4",
"semantic-release": "19.0.3",
"sinon": "9.2.4",
"standard": "16.0.3",
"uuid": "8.3.2"
+1
View File
@@ -0,0 +1 @@
COMPOSE_PROJECT_NAME=bch-wallet-consumer
+7 -7
View File
@@ -12,7 +12,7 @@ RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
@@ -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/
@@ -46,11 +46,11 @@ RUN npm set registry http://94.130.170.209:4873/
# Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer
# Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/ipfs-service-provider
WORKDIR /home/safeuser/ipfs-bch-wallet-consumer
# For development: switch to unstable branch
RUN git checkout ct-unstable
@@ -66,10 +66,10 @@ 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
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
#CMD ["npm", "start"]
+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.
+43 -12
View File
@@ -1,11 +1,11 @@
# Start the service with the command 'docker-compose up -d'
version: '2'
version: '3.9'
services:
mongo-ipfs-service:
image: mongo
container_name: mongo-ipfs-service
mongo-bch-consumer:
image: mongo:4.2.0
container_name: mongo-bch-consumer
ports:
- '5555:27017' # <host port>:<container port>
volumes:
@@ -13,9 +13,40 @@ services:
command: mongod --logpath=/dev/null # -- quiet
restart: always
ipfs-service:
build: .
container_name: ipfs-service
ipfs-bch-consumer:
image: ipfs/go-ipfs:v0.14.0
container_name: ipfs-bch-consumer
environment:
MY_ENV_VAR: 'placeholder'
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 2000mb
ports:
- 4001:4001
- 4003:4003
- 172.17.0.1:5001:5001
- 172.17.0.1:8080:8080
volumes:
- ../data/go-ipfs/data:/data/ipfs
# - ../../swarm.key:/data/ipfs/swarm.key
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
command:
[
'daemon',
'--migrate=true',
'--agent-version-suffix=docker',
'--enable-pubsub-experiment'
]
restart: always
bch-consumer:
#build: .
image: christroutner/ipfs-bch-wallet-consumer:v1.8.4
container_name: bch-consumer
logging:
driver: 'json-file'
options:
@@ -23,11 +54,11 @@ services:
max-file: '10'
mem_limit: 500mb
links:
- mongo-ipfs-service
- mongo-bch-consumer
- ipfs-bch-consumer
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
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
restart: always
+10 -9
View File
@@ -8,7 +8,7 @@
export MNEMONIC="olive two muscle bottom coral ancient wait legend bronze useful process session"
# The human readable name this IPFS node identifies as.
export COORD_NAME=ipfs-service-provider-generic
export COORD_NAME=bch-consumer-generic
# Allow this node to function as a circuit relay. It must not be behind a firewall.
#export ENABLE_CIRCUIT_RELAY=true
@@ -23,15 +23,16 @@ 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
export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
# Configure REST API port
export PORT=5001
export PORT=5010
# Production settings using external go-ipfs node.
export CONSUMER_ENV=production
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
export SVC_ENV=production
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
+36
View File
@@ -0,0 +1,36 @@
#!/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=1
# 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
export CONSUMER_ENV=production
# Lock the consumer into a preferred service provider.
#export PREFERRED_PROVIDER=12D3KooWEzE3HNH86WCb2Xocu6PgBCnSGUD7myJmuTHvoedMwp7K
npm start
+252 -24
View File
@@ -21,15 +21,15 @@ class BchAdapter {
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Connect the RPC handler when the event fires with new data.
this.eventEmitter.on('rpcData', this.rpcHandler)
// this.eventEmitter.on('rpcData', this.rpcHandler)
// Encapsulate dependencies
this.uid = uid
@@ -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()')
@@ -177,6 +208,60 @@ class BchAdapter {
}
}
// Query the UTXOs associated with an array of up to 20 addresses.
async getUtxosBulk (addrs) {
try {
// console.log('getUtxosBulk() addrs: ', addrs)
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
// Input validation
if (!Array.isArray(addrs)) {
throw new Error('addresses parameter must be an array')
}
if (addrs.length > 20) {
throw new Error('addresses parameter must not exceed 20 elements')
}
const rpcData = {
endpoint: 'utxosBulk',
addresses: addrs
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
// console.log(`getUtxosBulk() data: ${JSON.stringify(data, null, 2)}`)
return data
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in use-cases/bch.js/getUtxosBulk()')
throw err
}
}
async broadcast (hex) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
@@ -219,8 +304,11 @@ class BchAdapter {
}
}
async getTransactions (address) {
// Get the transaction history for an address.
async getTransactions (address, sortOrder = 'DESCENDING', page = 0) {
try {
console.log('Executing getTransaction() bch adapter')
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -230,8 +318,10 @@ class BchAdapter {
}
const rpcData = {
endpoint: 'transactions',
addresses: [address]
endpoint: 'txHistory',
address: address,
sortOrder,
page
}
// Generate a UUID for the call.
@@ -240,7 +330,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,8 +351,13 @@ class BchAdapter {
}
}
async getTransaction (txid) {
// Get details on an array of TXIDs.
async getTransaction (txids) {
try {
console.log(
`Getting txData on these txids: ${JSON.stringify(txids, null, 2)}`
)
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
@@ -272,8 +367,8 @@ class BchAdapter {
}
const rpcData = {
endpoint: 'transaction',
txid
endpoint: 'txData',
txids
}
// Generate a UUID for the call.
@@ -282,7 +377,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
@@ -294,11 +389,12 @@ class BchAdapter {
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
console.log(`data: ${JSON.stringify(data, null, 2)}`)
return data
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/bch.js/transaction()')
wlogger.error('Error in adapters/bch.js/getTransaction()')
throw err
}
}
@@ -345,6 +441,135 @@ class BchAdapter {
}
}
async utxoIsValid (utxo) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
const rpcData = {
endpoint: 'utxoIsValid',
utxo
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.log('utxoIsValid() error: ', err)
wlogger.error('Error in adapters/bch.js/utxoIsValid()')
throw err
}
}
async getTokenData (tokenId, withTxHistory = false) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
const rpcData = {
endpoint: 'getTokenData',
tokenId,
withTxHistory
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.log('getTokenData() error: ', err)
wlogger.error('Error in adapters/bch.js/getTokenData()')
throw err
}
}
// Get token icon and other media associated with the token.
async getTokenData2 (tokenId, updateCache) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
if (!selectedProvider) {
throw new Error('No BCH Wallet Service provider available yet.')
}
const rpcData = {
endpoint: 'getTokenData2',
tokenId,
updateCache
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.log('getTokenData2() error: ', err)
wlogger.error('Error in adapters/bch.js/getTokenData2()')
throw err
}
}
// Returns a promise that resolves to data when the RPC response is recieved.
async waitForRPCResponse (rpcId) {
try {
@@ -370,6 +595,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
-6
View File
@@ -52,12 +52,6 @@ class FullStackJWT {
// Get's a JWT token from FullStack.cash.
async getJWT () {
try {
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
if (process.env.TEST_TYPE === 'e2e') {
this.apiToken = 'faketoken'
return this.apiToken
}
// Log into the auth server.
await this.jwtLib.register()
+16 -7
View File
@@ -17,18 +17,19 @@ 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')
class Adapters {
constructor (localConfig = {}) {
// Dependency injection.
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// Encapsulate dependencies
this.ipfs = new IPFSAdapter(localConfig)
@@ -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)
@@ -57,7 +59,14 @@ class Adapters {
}
// Start the IPFS node.
await this.ipfs.start()
// Do not start these adapters if this is an e2e test.
if (this.config.env !== 'test') {
await this.ipfs.start()
}
console.log('Async Adapters have been started.')
return true
} catch (err) {
console.error('Error in adapters/index.js/start()')
throw err
+14 -7
View File
@@ -2,23 +2,26 @@
top-level IPFS library that combines the individual IPFS-based libraries.
*/
// Local libraries
const IpfsAdapter = require('./ipfs')
const IpfsCoordAdapter = require('./ipfs-coord')
const config = require('../../../config')
class IPFS {
constructor (localConfig = {}) {
// Dependency injection.
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
// )
// }
// Encapsulate dependencies
this.ipfsAdapter = new IpfsAdapter()
this.IpfsCoordAdapter = IpfsCoordAdapter
this.process = process
this.config = config
this.ipfsCoordAdapter = {} // placeholder
@@ -40,11 +43,15 @@ class IPFS {
// Start ipfs-coord
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
ipfs: this.ipfs,
eventEmitter: this.eventEmitter
tcpPort: this.config.ipfsTcpPort,
wsPort: this.config.ipfsWsPort
})
await this.ipfsCoordAdapter.start()
console.log('ipfs-coord is ready.')
// Subscribe to the chat pubsub channel
await this.ipfsCoordAdapter.subscribeToChat()
return true
} catch (err) {
console.error('Error in adapters/ipfs/index.js/start()')
+144 -24
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
@@ -29,12 +31,12 @@ class IpfsCoordAdapter {
'Instance of IPFS must be passed when instantiating ipfs-coord.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
// )
// }
// Encapsulate dependencies
this.IpfsCoord = IpfsCoord
@@ -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()
@@ -105,16 +121,16 @@ class IpfsCoordAdapter {
attachRPCRouter (router) {
try {
_this.ipfsCoord.privateLog = router
_this.ipfsCoord.adapters.orbit.privateLog = router
_this.ipfsCoord.adapters.pubsub.privateLog = router
} catch (err) {
console.error('Error in attachRPCRouter()')
throw err
}
}
// 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
@@ -125,6 +141,90 @@ class IpfsCoordAdapter {
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 {
// console.log('pollForBchServices() polling for BCH service')
// 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)
@@ -157,6 +257,8 @@ class IpfsCoordAdapter {
console.log(`---->BCH wallet service selected: ${thisPeer}`)
}
// console.log('preferredProvider: ', _this.config.preferredProvider)
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
@@ -166,6 +268,8 @@ class IpfsCoordAdapter {
_this.state.selectedServiceProvider = thisPeer
}
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.serviceProviders.filter(
(x) => x === thisPeer
@@ -179,22 +283,38 @@ 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.
}
}
// This method handles input coming in from other IPFS peers.
// It passes the data on to the REST API library by emitting an event.
peerInputHandler (data) {
try {
// console.log('peerInputHandler triggered with this data: ', data)
// peerInputHandler (data) {
// try {
// // console.log('peerInputHandler triggered with this data: ', data)
//
// this.eventEmitter.emit('rpcData', data)
// } catch (err) {
// console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
// // Do not throw error. This is a top-level function.
// }
// }
this.eventEmitter.emit('rpcData', data)
} catch (err) {
console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
// Do not throw error. This is a top-level function.
}
// Subscribe to the chat pubsub channel
async subscribeToChat () {
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
this.config.chatPubSubChan,
console.log,
this.ipfsCoord.thisNode
)
}
}
+42 -22
View File
@@ -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: 200 })
}
let ipfsOptions = ipfsOptionsEmbedded
if (this.config.isProduction) {
ipfsOptions = ipfsOptionsExternal
}
// Create a new IPFS node.
this.ipfs = await this.IPFS.create(ipfsOptions)
@@ -82,9 +102,9 @@ class IpfsAdapter {
console.error('Error in ipfs.js/start()')
// If IPFS crashes because the /blocks directory is full, wipe the directory.
if (err.message.includes('No space left on device')) {
this.rmBlocksDir()
}
// if (err.message.includes('No space left on device')) {
// this.rmBlocksDir()
// }
throw err
}
@@ -100,21 +120,21 @@ class IpfsAdapter {
// Dev Note: It's assumed this node is not pinning any data and that
// everything in this directory is transient. This folder will regularly
// fill up and prevent IPFS from starting.
rmBlocksDir () {
try {
const dir = `${IPFS_DIR}/blocks`
console.log(`Deleting ${dir} directory...`)
this.fs.rmdirSync(dir, { recursive: true })
console.log(`${dir} directory is deleted!`)
return true // Signal successful execution.
} catch (err) {
console.log('Error in rmBlocksDir()')
throw err
}
}
// rmBlocksDir () {
// try {
// const dir = `${IPFS_DIR}/blocks`
// console.log(`Deleting ${dir} directory...`)
//
// this.fs.rmdirSync(dir, { recursive: true })
//
// console.log(`${dir} directory is deleted!`)
//
// return true // Signal successful execution.
// } catch (err) {
// console.log('Error in rmBlocksDir()')
// throw err
// }
// }
}
module.exports = IpfsAdapter
+3 -1
View File
@@ -135,8 +135,10 @@ class LogsApi {
if (!filename || typeof filename !== 'string') {
throw new Error('filename must be a string')
}
// Throw an error if the file does not exist.
console.log('readLines() filename: ', filename)
// Throw an error if the file does not exist.
if (!_this.fs.existsSync(filename)) {
throw new Error('file does not exist')
}
+267
View File
@@ -0,0 +1,267 @@
/*
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
+25 -17
View File
@@ -5,26 +5,23 @@
*/
// Public npm libraries.
const EventEmitter = require('events')
// 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
// this.eventEmitter = new EventEmitter()
// localConfig.eventEmitter = this.eventEmitter
// Initialize Clean Architecture classes. Inject dependencies.
this.adapters = new Adapters(localConfig)
@@ -32,14 +29,14 @@ class Controllers {
this.useCases = new UseCases(localConfig)
}
async attachControllers (app) {
// Wait for any startup processes to complete for the Adapters libraries.
// Spin up any adapter libraries that have async startup needs.
async initAdapters () {
await this.adapters.start()
}
// Attach the REST controllers to the Koa app.
// this.attachRESTControllers(app)
this.attachRPCControllers()
// Run any Use Cases to startup the app.
async initUseCases () {
await this.useCases.start()
}
// Top-level function for this library.
@@ -54,6 +51,17 @@ class Controllers {
restControllers.attachRESTControllers(app)
}
// Attach any other controllers other than REST API controllers.
async attachControllers (app) {
// 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)
this.attachRPCControllers()
}
// Add the JSON RPC router to the ipfs-coord adapter.
attachRPCControllers () {
const jsonRpcController = new JSONRPC({
+5
View File
@@ -1,5 +1,8 @@
/*
This is the JSON RPC router for the users API
CT 3/5/22: This library can probably be deleted. Handling of the /about endpoint
is now directly controlled by the ipfs-coord library.
*/
// Public npm libraries
@@ -33,6 +36,8 @@ class AboutRPC {
// This is a bit different than other router libraries, because there is
// only one response, which is a string about this node.
async aboutRouter (rpcData) {
console.log('debugging: aboutRouter from ipfs-service-provider triggered')
return {
success: true,
status: 200,
+14 -1
View File
@@ -90,13 +90,14 @@ class JSONRPC {
}
// Process response from REST API.
_this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
// Log the incoming JSON RPC command.
wlogger.info(
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
)
}
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
// Added the property "from" to the parsedData object;
// necessary for calculating rate limits (based on the IPFS ID).
@@ -105,6 +106,16 @@ class JSONRPC {
// Default return string
let retObj = _this.defaultResponse()
// Forward data on to bch adapter if this is the response of a BCH query.
try {
if (parsedData.payload.result.method === 'bch') {
console.log('routing to BCH adapter')
retObj = await _this.adapters.bch.rpcHandler(parsedData)
}
} catch (err) {
/* exit quietly */
}
// Route the command to the appropriate route handler.
switch (parsedData.payload.method) {
case 'users':
@@ -116,6 +127,8 @@ class JSONRPC {
case 'about':
retObj = await _this.aboutController.aboutRouter(parsedData)
break
case 'bch':
retObj = await _this.adapters.bch.rpcHandler(parsedData)
// default:
// // Process response from REST API.
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
+280 -25
View File
@@ -3,6 +3,7 @@
*/
const { wlogger } = require('../../../adapters/wlogger')
const config = require('../../../../config')
// let _this
@@ -23,10 +24,7 @@ class BchRESTControllerLib {
}
// Encapsulate dependencies
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
this.config = config
}
/**
@@ -51,10 +49,26 @@ 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
// Throw an error, if the provider has been set by an environment variable.
if (this.config.preferredProvider) {
throw new Error('Consumer has preferredProvider set in environment variable. Refusing to switch providers.')
}
await this.adapters.bch.selectProvider(providerId)
const body = {
@@ -220,6 +234,103 @@ class BchRESTControllerLib {
}
}
/**
* @api {post} /bch/utxosBulk UTXOs Bulk
* @apiName UTXOs Bulk
* @apiGroup REST BCH
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
* with token information.
* @apiDescription This endpoint is the same as the /bch/utxos endpoint, but
* it allows an array of up to 20 addresses to be queried at the same time.
* This reduces the number of JSON RPC calls, and is very handy
* for HD wallets that need to quickly scan a lot of addresses.
*
* Given an address, this endpoint will return an object with thre following
* properties:
*
* - addresses: [] - the addresses these UTXOs are associated with
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
* not spent, to be safe.
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
* - type1: {}
* - tokens: [] - SLP token Type 1 tokens.
* - mintBatons: [] - SLP token Type 1 mint batons.
* - nft: {}
* - tokens: [] - NFT tokens
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* [
* {
* "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"],
* "bchUtxos":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "tx_pos":0,
* "value":1000,
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
* "vout":0,
* "isValid":false
* }
* ],
* "nullUtxos":[
*
* ],
* "slpUtxos":{
* "type1":{
* "mintBatons":[
*
* ],
* "tokens":[
*
* ]
* },
* "nft":{
* "groupMintBatons":[
*
* ],
* "groupTokens":[
*
* ],
* "tokens":[
*
* ]
* }
* }
* }
* ]
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
async utxosBulk (ctx) {
try {
const addrs = ctx.request.body.addresses
// console.log('address: ', address)
const utxos = await this.adapters.bch.getUtxosBulk(addrs)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = utxos
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {post} /bch/broadcast Broadcast
* @apiName Broadcast
@@ -260,10 +371,17 @@ class BchRESTControllerLib {
/**
*
* @api {post} /bch/transactions Transactions
* @apiName Transactions
* @api {post} /bch/txHistory TX History
* @apiName TX History
* @apiGroup REST BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
* It returns the transaction history for an address. This list of TXIDs is
* sorted and paginated.
*
* There are three possible inputs:
* - address: (required) the address to query for a transaction history
* - sortOrder: (optional) will sort results in 'DECENDING' (default) or 'ASCENDING' order.
* - page: (optional) will return a 'page' of 100 results. Default is 0
*
* Given the 'addresses' property returns an array of objects
* with the following properties
@@ -278,7 +396,7 @@ class BchRESTControllerLib {
* Note: For a single address pass the 'addresses' of string type
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/transactions
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/txHistory
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
@@ -306,11 +424,20 @@ class BchRESTControllerLib {
* "error": "Unprocessable Entity"
* }
*/
async transactions (ctx) {
async txHistory (ctx) {
try {
const addr = ctx.request.body.address
// console.log('transactions REST API handler called.')
// console.log(`body: ${JSON.stringify(ctx.request.body, null, 2)}`)
const data = await this.adapters.bch.getTransactions(addr)
const addr = ctx.request.body.address
const sortOrder = ctx.request.body.sortOrder
const page = ctx.request.body.page
const data = await this.adapters.bch.getTransactions(
addr,
sortOrder,
page
)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
@@ -320,11 +447,14 @@ class BchRESTControllerLib {
}
/**
* @api {post} /bch/transaction Transaction
* @apiName Transaction
* @api {post} /bch/txData TX Data
* @apiName txData
* @apiGroup REST BCH
* @apiDescription Get data about a specific transaction.
* Given a transaction id this endpoint will return an object
* @apiDescription Get expanded transaction data for an array of transaction
* IDs. Each call is limited to 20 TXIDs or less.
*
* Get data about specific transactions.
* Given an array of transaction IDs this endpoint will return an array of objects
* with the following properties
*
* - txid: "" - Transaction ID
@@ -342,11 +472,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 +525,7 @@ class BchRESTControllerLib {
* "time":1568338904,
* "blocktime":1568338904,
* "isValidSLPTx":false
* }
* }]
*
* @apiError UnprocessableEntity Missing required parameters
*
@@ -406,11 +536,19 @@ class BchRESTControllerLib {
* "error": "Unprocessable Entity"
* }
*/
async transaction (ctx) {
async txData (ctx) {
try {
const txid = ctx.request.body.txid
console.log(
`txData called with this body data: ${JSON.stringify(
ctx.request.body,
null,
2
)}`
)
const data = await this.adapters.bch.getTransaction(txid)
const txids = ctx.request.body.txids
const data = await this.adapters.bch.getTransaction(txids)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
ctx.body = data
@@ -420,10 +558,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 +573,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:
* {
@@ -451,8 +589,9 @@ class BchRESTControllerLib {
* "pubkey": {
* "success": true,
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
* }
* }
* }
*
* }
*/
async pubKey (ctx) {
@@ -460,7 +599,123 @@ 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) {
this.handleError(ctx, err)
}
}
/**
* @api {REST} /bch/utxoIsValid utxoIsValid
* @apiPermission public
* @apiName utxoIsValid
* @apiGroup REST BCH
* @apiDescription Verify if UTXO is valid
* Given a UTXO object (txid and vout), a full node is queried to verify that
* the UTXO still exists in the mempool (true), or if it has been spent (false).
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - success: - Request status
* - isValid: - Boolean: true or false
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxoIsValid", "utxo": {"tx_hash": "17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a", "tx_pos": 0}}}
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "success": true,
* "status": 200,
* "endpoint": "utxoIsValid",
* "isValid": true
* }
* }
*
* }
*/
async utxoIsValid (ctx) {
try {
const utxo = ctx.request.body.utxo
const data = await this.adapters.bch.utxoIsValid(utxo)
// console.log(`utxoIsValid data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {REST} /bch getTokenData
* @apiPermission public
* @apiName getTokenData
* @apiGroup REST BCH
* @apiDescription Get data associated with a token
*
* Given a token ID, this endpoint will retrieve the IPFS CIDs associated with
* the tokens mutable and immutable data. This is extension of the PS002
* specification for mutable data for tokens:
* https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps002-slp-mutable-data.md
*
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "withTxHistory": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData
*/
async getTokenData (ctx) {
try {
const tokenId = ctx.request.body.tokenId
const withTxHistory = !!ctx.request.body.withTxHistory
const data = await this.adapters.bch.getTokenData(tokenId, withTxHistory)
console.log(`getTokenData data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
this.handleError(ctx, err)
}
}
/**
* @api {REST} /bch getTokenData2
* @apiPermission public
* @apiName getTokenData2
* @apiGroup REST BCH
* @apiDescription Get token icon and other media
*
* Get the icon for a token, given it's token ID.
* This function expects a string input of a token ID property.
* This function returns an object with a tokenIcon property that contains
* the URL to the icon.
*
* The output object always have these properties:
* - tokenIcon: A url to the token icon, if it exists.
* - tokenStats: Data about the token from psf-slp-indexer.
* - optimizedTokenIcon: An alternative, potentially more optimal, url to the token icon, if it exists.
* - iconRepoCompatible: true if the token icon is available via token.bch.sx
* - ps002Compatible: true if the token icon is compatible with PS007 specification.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d" }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "updateCache": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
*/
async getTokenData2 (ctx) {
try {
const tokenId = ctx.request.body.tokenId
const updateCache = ctx.request.body.updateCache
console.log('getTokenData2() updateCache: ', updateCache)
const data = await this.adapters.bch.getTokenData2(tokenId, updateCache)
console.log(`getTokenData2 data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
} catch (err) {
+26 -6
View File
@@ -55,10 +55,14 @@ class BchRouter {
this.router.post('/provider', this.postProvider)
this.router.post('/balance', this.postBalance)
this.router.post('/utxos', this.postUtxos)
this.router.post('/utxosBulk', this.postUtxosBulk)
this.router.post('/broadcast', this.postBroadcast)
this.router.post('/transactions', this.postTransactions)
this.router.post('/transaction', this.postTransaction)
this.router.post('/txHistory', this.postTxHistory)
this.router.post('/txData', this.postTxData)
this.router.post('/pubkey', this.postPubKey)
this.router.post('/utxoIsValid', this.utxoIsValid)
this.router.post('/getTokenData', this.getTokenData)
this.router.post('/getTokenData2', this.getTokenData2)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -81,21 +85,37 @@ class BchRouter {
await _this.bchRESTController.utxos(ctx, next)
}
async postUtxosBulk (ctx, next) {
await _this.bchRESTController.utxosBulk(ctx, next)
}
async postBroadcast (ctx, next) {
await _this.bchRESTController.broadcast(ctx, next)
}
async postTransactions (ctx, next) {
await _this.bchRESTController.transactions(ctx, next)
async postTxHistory (ctx, next) {
await _this.bchRESTController.txHistory(ctx, next)
}
async postTransaction (ctx, next) {
await _this.bchRESTController.transaction(ctx, next)
async postTxData (ctx, next) {
await _this.bchRESTController.txData(ctx, next)
}
async postPubKey (ctx, next) {
await _this.bchRESTController.pubKey(ctx, next)
}
async utxoIsValid (ctx, next) {
await _this.bchRESTController.utxoIsValid(ctx, next)
}
async getTokenData (ctx, next) {
await _this.bchRESTController.getTokenData(ctx, next)
}
async getTokenData2 (ctx, next) {
await _this.bchRESTController.getTokenData2(ctx, next)
}
}
module.exports = BchRouter
+10
View File
@@ -13,6 +13,8 @@ const ContactRESTController = require('./contact')
const LogsRESTController = require('./logs')
const BchRESTController = require('./bch')
const IpfsRESTController = require('./ipfs')
const P2wdbRESTController = require('./p2wdb')
const PriceRESTController = require('./price')
class RESTControllers {
constructor (localConfig = {}) {
@@ -62,6 +64,14 @@ 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)
// Attach the REST API Controllers associated with the /price route
const priceRESTController = new PriceRESTController(dependencies)
priceRESTController.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
@@ -0,0 +1,108 @@
/*
REST API Controller library for the /bch route
*/
const axios = require('axios')
const { wlogger } = require('../../../adapters/wlogger')
// let _this
class PriceRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /price REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /price REST Controller.'
)
}
// Encapsulate dependencies
this.axios = axios
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// _this = this
}
/**
* @api {get} /price/usd Get the USD price of BCH
* @apiName Get the USD price of BCH
* @apiGroup Price
* @apiDescription Get the USD price of BCH from Coinbase.
*
*
* @apiExample Example usage:
* curl -X GET "http://localhost:5005/price/usd" -H "accept: application/json"
*
*/
async getUSD (ctx) {
try {
// Request options
const opt = {
method: 'get',
baseURL: 'https://api.coinbase.com/v2/',
url: '/exchange-rates?currency=BCH',
timeout: 15000
}
const response = await this.axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
ctx.body = { usd: Number(response.data.data.rates.USD) }
} catch (err) {
// Write out error to error log.
wlogger.error('Error in GET /price/usd.', err)
this.handleError(ctx, err)
}
}
// Get the XEC price.
// TODO: Add API docs.
async getXecPrice (ctx) {
try {
// Request options
const opt = {
method: 'get',
baseURL: 'https://api.coinex.com',
url: '/v1/market/ticker?market=xecusdt',
timeout: 15000
}
//
const response = await this.axios.request(opt)
console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
ctx.body = { usd: Number(response.data.data.ticker.last) }
} catch (err) {
// Write out error to error log.
wlogger.error('Error in GET /price/xecusd.', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
ctx.throw(err.status, err.message)
} else {
ctx.throw(err.status)
}
} else {
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
}
}
module.exports = PriceRESTControllerLib
+71
View File
@@ -0,0 +1,71 @@
/*
REST API library for /bch route.
*/
// Public npm libraries.
const Router = require('koa-router')
// Local libraries.
const PriceRESTControllerLib = require('./controller')
const Validators = require('../middleware/validators')
let _this
class PriceRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating Price REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating Price REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.priceRESTController = new PriceRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/price'
this.router = new Router({ prefix: baseUrl })
_this = this
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attaching REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.get('/usd', this.getPrice)
this.router.get('/xecusd', this.getXecPrice)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
async getPrice (ctx, next) {
await _this.priceRESTController.getUSD(ctx, next)
}
async getXecPrice (ctx, next) {
await _this.priceRESTController.getXecPrice(ctx, next)
}
}
module.exports = PriceRouter
+19 -6
View File
@@ -14,16 +14,29 @@ class UseCases {
'Instance of adapters must be passed in when instantiating Use Cases library.'
)
}
this.eventEmitter = localConfig.eventEmitter
if (!this.eventEmitter) {
throw new Error(
'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
)
}
// this.eventEmitter = localConfig.eventEmitter
// if (!this.eventEmitter) {
// throw new Error(
// 'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
// )
// }
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
async start () {
// try {
console.log('Async Use Cases have been started.')
return true
// } catch (err) {
// console.error('Error in use-cases/index.js/start()')
// // console.log(err)
// throw err
// }
}
}
module.exports = UseCases
+1
View File
@@ -7,6 +7,7 @@
// Public npm libraries
const assert = require('chai').assert
const axios = require('axios').default
// const sinon = require('sinon')
// Local support libraries
const config = require('../../../config')
+98 -39
View File
@@ -7,7 +7,7 @@
This test will query each REST API endpoint and ensure they are working correctly.
*/
const SERVER = 'http://localhost:5001'
const SERVER = 'http://localhost:5005'
const axios = require('axios')
const assert = require('chai').assert
@@ -23,8 +23,8 @@ describe('#BCH E2E Tests', () => {
const result = await axios.get(url)
// console.log(`data: ${JSON.stringify(result.data, null, 2)}`)
assert.isAbove(result.data.status.state.serviceProviders.length, 0)
assert.isOk(result.data.status.state.selectedServiceProvider)
assert.isAbove(result.data.status.serviceProviders.length, 0)
assert.isOk(result.data.status.selectedProvider)
})
})
@@ -56,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('#txHistory', () => {
it('should get transaction history for an address, and sort by default in descending order.', async () => {
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
const body = {
address
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert expected values and properties.
assert.equal(result.data.status, 200)
assert.equal(result.data.success, true)
assert.equal(result.data.address, address)
assert.isArray(result.data.txs)
// Assert expected sorting
assert.isAbove(result.data.txs[0].height, result.data.txs[1].height)
})
it('should sort in ascending order.', async () => {
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
const body = {
address,
sortOrder: 'ASCENDING'
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert expected values and properties.
assert.equal(result.data.status, 200)
assert.equal(result.data.success, true)
assert.equal(result.data.address, address)
assert.isArray(result.data.txs)
// Assert expected sorting
assert.isAbove(result.data.txs[1].height, result.data.txs[0].height)
})
it('should paginate an address with deep tx history', async () => {
const body = {
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert there is no more than 100 entries
assert.isBelow(result.data.txs.length, 101)
})
it('should get the second page of results', async () => {
const body = {
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d',
page: 1
}
const url = `${SERVER}/bch/txHistory`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
// Assert there is no more than 100 entries
assert.isBelow(result.data.txs.length, 101)
})
})
describe('#txData', () => {
it('should get transaction details for a txid', async () => {
const body = {
txids: [
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
]
}
const url = `${SERVER}/bch/txData`
const result = await axios.post(url, body)
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
assert.equal(result.data.status, 200)
assert.isArray(result.data.txData)
assert.property(result.data.txData[0], 'txid')
assert.property(result.data.txData[0], 'vin')
})
})
})
+54
View File
@@ -0,0 +1,54 @@
/*
Unit tests for the adapters index.js library
*/
// Global npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local libraries
const Adapters = require('../../../src/adapters')
describe('#adapters', () => {
let uut, sandbox
beforeEach(() => {
uut = new Adapters()
sandbox = sinon.createSandbox()
})
afterEach(() => {
sandbox.restore()
})
describe('#start', () => {
it('should start the async adapters', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
sandbox.stub(uut.ipfs, 'start').resolves()
const result = await uut.start()
assert.equal(result, true)
})
it('should catch and throw an error', async () => {
try {
// Force an error
uut.config.getJwtAtStartup = false
uut.config.env = 'dev'
sandbox.stub(uut.ipfs, 'start').rejects(new Error('test error'))
await uut.start()
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
})
})
+179 -13
View File
@@ -54,19 +54,19 @@ describe('#bch-use-case', () => {
}
})
it('should throw an error if adapters are not passed in', () => {
try {
const ipfs = {}
uut = new BchAdapter({ ipfs })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of an EventEmitter must be passed when instantiating the adapters.'
)
}
})
// it('should throw an error if adapters are not passed in', () => {
// try {
// const ipfs = {}
// uut = new BchAdapter({ ipfs })
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(
// err.message,
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
// )
// }
// })
})
describe('#waitForRPCResponse', () => {
@@ -165,6 +165,82 @@ describe('#bch-use-case', () => {
})
})
describe('#getUtxos', () => {
it('should get the UTXOs of an address', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addr = 'addr'
const result = await uut.getUtxos(addr)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getUtxos()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getUtxosBulk', () => {
it('should get the UTXOs of an address', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const addrs = ['addr']
const result = await uut.getUtxosBulk(addrs)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should throw an error if addresses is not an array', async () => {
try {
await uut.getUtxosBulk()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'addresses parameter must be an array')
}
})
it('should throw an error if addresses array is larger than 20 elements', async () => {
try {
const addrs = []
for (let i = 0; i < 25; i++) {
addrs.push(i)
}
await uut.getUtxosBulk(addrs)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'addresses parameter must not exceed 20 elements')
}
})
})
describe('#broadcast', () => {
it('should broadcast a transaction', async () => {
// Force connection to a wallet service
@@ -284,4 +360,94 @@ describe('#bch-use-case', () => {
}
})
})
describe('#utxoIsValid', () => {
it('should validate a utxo', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const utxo = { a: 'b' }
const result = await uut.utxoIsValid(utxo)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.utxoIsValid()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getTokenData', () => {
it('should validate a utxo', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const tokenId = 'blah'
const result = await uut.getTokenData(tokenId)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getTokenData()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
describe('#getTokenData2', () => {
it('should validate a utxo', async () => {
// Force connection to a wallet service
uut.ipfs.ipfsCoordAdapter.state = {
selectedServiceProvider: 'abc123'
}
// Mock depenencies
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
const tokenId = 'blah'
const result = await uut.getTokenData2(tokenId)
// console.log('result: ', result)
assert.equal(result.key, 'value')
})
it('should catch and throw an error', async () => {
try {
await uut.getTokenData2()
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.equal(err.message, 'test error')
}
})
})
})
+64 -36
View File
@@ -9,7 +9,7 @@ const cloneDeep = require('lodash.clonedeep')
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
const IPFSMock = require('../mocks/ipfs-mock')
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
const EventEmitter = require('events')
const config = require('../../../config')
const mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
describe('#IPFS', () => {
@@ -19,7 +19,7 @@ describe('#IPFS', () => {
beforeEach(() => {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs, eventEmitter: new EventEmitter() })
uut = new IPFSCoordAdapter({ ipfs })
sandbox = sinon.createSandbox()
@@ -29,7 +29,8 @@ describe('#IPFS', () => {
afterEach(() => {
sandbox.restore()
clearInterval(uut.pollServiceInterval)
clearInterval(uut.pollBchServiceInterval)
clearInterval(uut.pollP2wdbServiceInterval)
})
describe('#constructor', () => {
@@ -46,19 +47,19 @@ describe('#IPFS', () => {
}
})
it('should throw an error if EventEmitter instance is not included', () => {
try {
const ipfs = IPFSMock.create()
uut = new IPFSCoordAdapter({ ipfs })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
)
}
})
// it('should throw an error if EventEmitter instance is not included', () => {
// try {
// const ipfs = IPFSMock.create()
// uut = new IPFSCoordAdapter({ ipfs })
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(
// err.message,
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
// )
// }
// })
})
describe('#start', () => {
@@ -85,6 +86,16 @@ describe('#IPFS', () => {
assert.equal(result, true)
})
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
uut.config.isProduction = true
// Mock dependencies.
uut.IpfsCoord = IPFSCoordMock
const result = await uut.start()
// console.log('result: ', result)
assert.equal(result, true)
config.isProduction = false
})
})
describe('#attachRPCRouter', () => {
@@ -98,8 +109,9 @@ describe('#IPFS', () => {
}
},
adapters: {
orbit: {
privateLog: () => {}
pubsub: {
privateLog: () => {
}
}
}
}
@@ -120,12 +132,12 @@ describe('#IPFS', () => {
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
})
describe('#pollForServices', () => {
describe('#pollForBchServices', () => {
it('should find and select the wallet service', () => {
uut.ipfsCoord = {
thisNode: {
@@ -134,7 +146,7 @@ describe('#IPFS', () => {
}
}
uut.pollForServices()
uut.pollForBchServices()
// It should fine the service in the mocked data.
assert.equal(
@@ -144,28 +156,44 @@ describe('#IPFS', () => {
})
it('should catch and report errors', () => {
uut.pollForServices()
uut.pollForBchServices()
assert.isOk(true, 'Not throwing an error is a success.')
})
})
describe('#peerInputHandler', () => {
it('should emit an event trigger', () => {
const data = 'some data'
// describe('#peerInputHandler', () => {
// it('should emit an event trigger', () => {
// const data = 'some data'
//
// uut.peerInputHandler(data)
//
// assert.isOk(true, 'Not throwing an error is a success')
// })
//
// it('should catch and report errors', () => {
// // Force an error
// sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
//
// uut.peerInputHandler()
//
// assert.isOk(true, 'Not throwing an error is a success.')
// })
// })
uut.peerInputHandler(data)
describe('#subscribeToChat', () => {
it('should subscribe to the chat channel', async () => {
// Mock dependencies
uut.ipfsCoord = {
adapters: {
pubsub: {
subscribeToPubsubChannel: async () => {
}
}
}
}
assert.isOk(true, 'Not throwing an error is a success')
})
it('should catch and report errors', () => {
// Force an error
sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
uut.peerInputHandler()
assert.isOk(true, 'Not throwing an error is a success.')
await uut.subscribeToChat()
})
})
})
+57 -23
View File
@@ -4,10 +4,11 @@
const assert = require('chai').assert
const sinon = require('sinon')
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
const IPFSMock = require('../mocks/ipfs-mock')
const config = require('../../../config')
// config.isProduction = true;
describe('#IPFS-adapter', () => {
let uut
let sandbox
@@ -18,7 +19,27 @@ describe('#IPFS-adapter', () => {
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
afterEach(() => {
sandbox.restore()
})
describe('#constructor', () => {
it('should instantiate IPFS Lib in dev mode.', async () => {
const _uut = new IPFSLib()
assert.exists(_uut)
assert.isFunction(_uut.start)
assert.isFunction(_uut.stop)
})
it('should instantiate dev IPFS Lib in production mode.', async () => {
config.isProduction = true
const _uut = new IPFSLib()
assert.exists(_uut)
assert.isFunction(_uut.start)
assert.isFunction(_uut.stop)
config.isProduction = false
})
})
describe('#start', () => {
it('should return a promise that resolves into an instance of IPFS.', async () => {
@@ -33,6 +54,18 @@ describe('#IPFS-adapter', () => {
assert.property(result, 'config')
})
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
// Mock dependencies.
uut.IPFS = IPFSMock
uut.config.isProduction = true
const result = await uut.start()
// console.log('result: ', result)
assert.equal(uut.isReady, true)
assert.property(result, 'config')
})
it('should catch and throw an error', async () => {
try {
// Force an error
@@ -52,7 +85,8 @@ describe('#IPFS-adapter', () => {
it('should stop the IPFS node', async () => {
// Mock dependencies
uut.ipfs = {
stop: () => {}
stop: () => {
}
}
const result = await uut.stop()
@@ -61,24 +95,24 @@ describe('#IPFS-adapter', () => {
})
})
describe('#rmBlocksDir', () => {
it('should delete the /blocks directory', () => {
const result = uut.rmBlocksDir()
assert.equal(result, true)
})
it('should catch and throw an error', () => {
try {
// Force an error
sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
uut.rmBlocksDir()
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(err.message, 'test error')
}
})
})
// describe('#rmBlocksDir', () => {
// it('should delete the /blocks directory', () => {
// const result = uut.rmBlocksDir()
//
// assert.equal(result, true)
// })
//
// it('should catch and throw an error', () => {
// try {
// // Force an error
// sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
//
// uut.rmBlocksDir()
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.equal(err.message, 'test error')
// }
// })
// })
})
+25 -32
View File
@@ -32,19 +32,18 @@ describe('#LogsApiLib', () => {
})
it('should return log', async () => {
try {
const pass = 'test'
const result = await uut.getLogs(pass)
// console.log('result', result)
// Mock dependencies
sandbox.stub(uut, 'generateFileName').returns(`${__dirname.toString()}/../mocks/adapters/fake-log`)
assert.isTrue(result.success)
assert.isArray(result.data)
assert.property(result.data[0], 'message')
assert.property(result.data[0], 'level')
assert.property(result.data[0], 'timestamp')
} catch (err) {
assert(false, 'Unexpected result')
}
const pass = 'test'
const result = await uut.getLogs(pass)
// console.log('result', result)
assert.isTrue(result.success)
assert.isArray(result.data)
assert.property(result.data[0], 'message')
assert.property(result.data[0], 'level')
assert.property(result.data[0], 'timestamp')
})
it('should return false if files are not found!', async () => {
@@ -201,31 +200,25 @@ describe('#LogsApiLib', () => {
})
it('should ignore fileReader callback errors', async () => {
try {
// https://sinonjs.org/releases/latest/stubs/
// About yields
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
// https://sinonjs.org/releases/latest/stubs/
// About yields
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
const fileName = context.fileName
const result = await uut.readLines(fileName)
assert.isArray(result)
} catch (err) {
assert.fail('Unexpected result')
}
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
const result = await uut.readLines(fileName)
assert.isArray(result)
})
it('should return data', async () => {
try {
const fileName = context.fileName
const result = await uut.readLines(fileName)
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
assert.isArray(result)
assert.property(result[1], 'message')
assert.property(result[1], 'level')
assert.property(result[1], 'timestamp')
} catch (err) {
assert.fail('Unexpected result')
}
const result = await uut.readLines(fileName)
assert.isArray(result)
assert.property(result[1], 'message')
assert.property(result[1], 'level')
assert.property(result[1], 'timestamp')
})
})
})
@@ -333,7 +333,7 @@ describe('#UserRPC', () => {
assert.equal(result.endpoint, 'updateUser')
assert.equal(result.success, false)
assert.equal(result.status, 422)
assert.include(result.message, 'Cannot read property')
assert.include(result.message, 'Cannot read')
})
})
@@ -375,7 +375,7 @@ describe('#UserRPC', () => {
assert.equal(result.endpoint, 'getUser')
assert.equal(result.success, false)
assert.equal(result.status, 422)
assert.include(result.message, 'Cannot read property')
assert.include(result.message, 'Cannot read')
})
})
@@ -177,6 +177,42 @@ describe('#BCH-REST-Controller', () => {
})
})
describe('#utxosBulk', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getUtxosBulk')
.rejects(new Error('test error'))
ctx.request.body = {
address: 'blah'
}
await uut.utxosBulk(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'getUtxosBulk').resolves({ status: 200 })
ctx.request.body = {
address: 'blah'
}
await uut.utxosBulk(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#broadcast', () => {
it('should return 422 status on arbitrary error', async () => {
try {
@@ -213,7 +249,7 @@ describe('#BCH-REST-Controller', () => {
})
})
describe('#transactions', () => {
describe('#txHistory', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
@@ -225,7 +261,7 @@ describe('#BCH-REST-Controller', () => {
address: 'blah'
}
await uut.transactions(ctx)
await uut.txHistory(ctx)
assert.fail('Unexpected result')
} catch (err) {
@@ -242,14 +278,14 @@ describe('#BCH-REST-Controller', () => {
address: 'blah'
}
await uut.transactions(ctx)
await uut.txHistory(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#transaction', () => {
describe('#txData', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
@@ -261,7 +297,7 @@ describe('#BCH-REST-Controller', () => {
txid: 'blah'
}
await uut.transaction(ctx)
await uut.txData(ctx)
assert.fail('Unexpected result')
} catch (err) {
@@ -280,7 +316,7 @@ describe('#BCH-REST-Controller', () => {
txid: 'blah'
}
await uut.transaction(ctx)
await uut.txData(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
@@ -322,4 +358,112 @@ describe('#BCH-REST-Controller', () => {
assert.equal(ctx.status, 200)
})
})
describe('#utxoIsValid', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'utxoIsValid')
.rejects(new Error('test error'))
ctx.request.body = {
utxo: 'blah'
}
await uut.utxoIsValid(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'utxoIsValid').resolves({ status: 200 })
ctx.request.body = {
utxo: 'blah'
}
await uut.utxoIsValid(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#getTokenData', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getTokenData')
.rejects(new Error('test error'))
ctx.request.body = {
tokenId: 'blah'
}
await uut.getTokenData(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'getTokenData').resolves({ status: 200 })
ctx.request.body = {
tokenId: 'blah'
}
await uut.getTokenData(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
describe('#getTokenData2', () => {
it('should return 422 status on arbitrary error', async () => {
try {
// Force an error
sandbox
.stub(uut.adapters.bch, 'getTokenData2')
.rejects(new Error('test error'))
ctx.request.body = {
tokenId: 'blah'
}
await uut.getTokenData2(ctx)
assert.fail('Unexpected result')
} catch (err) {
console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
sandbox.stub(uut.adapters.bch, 'getTokenData2').resolves({ status: 200 })
ctx.request.body = {
tokenId: 'blah'
}
await uut.getTokenData2(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
})
})
})
@@ -14,7 +14,8 @@ let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('Contact', () => {
before(async () => {})
before(async () => {
})
beforeEach(() => {
uut = new ContactController()
@@ -36,7 +37,7 @@ describe('Contact', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -14,7 +14,8 @@ let ctx
const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('Logapi', () => {
before(async () => {})
before(async () => {
})
beforeEach(() => {
uut = new LogsApiController()
@@ -35,7 +36,7 @@ describe('Logapi', () => {
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -69,7 +69,7 @@ describe('#Users-REST-Controller', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -91,9 +91,9 @@ describe('#Users-REST-Controller', () => {
assert.property(ctx.response.body, 'user')
assert.property(ctx.response.body, 'token')
// Used by downstream tests.
// testUser = ctx.response.body.user
// console.log('testUser: ', testUser)
// Used by downstream tests.
// testUser = ctx.response.body.user
// console.log('testUser: ', testUser)
})
})
@@ -181,7 +181,7 @@ describe('#Users-REST-Controller', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -223,7 +223,7 @@ describe('#Users-REST-Controller', () => {
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
+65
View File
@@ -0,0 +1,65 @@
/*
Unit tests for the bin/server.js file
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Local libraries
const Server = require('../../../bin/server')
describe('#server', () => {
let uut, sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new Server()
})
afterEach(() => sandbox.restore())
describe('#startServer', () => {
it('should start the server', async () => {
// Mock dependencies
sandbox.stub(uut.mongoose, 'connect').resolves()
sandbox.stub(uut.controllers, 'initAdapters').resolves()
sandbox.stub(uut.controllers, 'initUseCases').resolves()
sandbox.stub(uut.controllers, 'attachRESTControllers').resolves()
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
sandbox.stub(uut.controllers, 'attachControllers').resolves()
uut.config.env = 'dev'
const result = await uut.startServer()
// console.log('result: ', result)
assert.property(result, 'env')
// Turn off the server.
uut.server.close()
// Restor config env
uut.config.env = 'test'
})
it('should exit on failure', async () => {
// Force an error
sandbox.stub(uut.mongoose, 'connect').rejects(new Error('test error'))
// Prevent default behavior of exiting the program.
sandbox.stub(uut, 'sleep').resolves()
sandbox.stub(uut.process, 'exit').returns()
await uut.startServer()
// Not throwing an error is a success
})
})
describe('#sleep', () => {
it('should execute', async () => {
await uut.sleep(1)
})
})
})
+70
View File
@@ -0,0 +1,70 @@
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.907Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.909Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.910Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.912Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.915Z"}
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.979Z"}
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.980Z"}
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.981Z"}
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.982Z"}
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.983Z"}
{"level":"error","message":"Error in lib/nodemailer.js/validateEmailArray()","timestamp":"2022-06-27T17:36:59.002Z"}
{"level":"error","message":"Error in lib/nodemailer.js/validateEmailArray()","timestamp":"2022-06-27T17:36:59.003Z"}
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.005Z"}
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.006Z"}
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.008Z"}
{"level":"info","message":"Warning: Can not send JSON RPC response. Can not determine which peer this message came from.","timestamp":"2022-06-27T17:36:59.353Z"}
{"level":"info","message":"Rejecting invalid JSON RPC command.","timestamp":"2022-06-27T17:36:59.354Z"}
{"level":"info","message":"JSON RPC received from peerA, ID: ea442e78-2297-466a-823b-9cbb12c8ddd9, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.355Z"}
{"level":"error","message":"Error in rpc router(): test error","stack":"Error: test error\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/a10-rpc.unit.js:110:49)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:36:59.357Z"}
{"level":"info","message":"JSON RPC received from peerA, ID: 9ef6ad90-7fc3-4cb3-8769-4498e5512b3d, type: request, method: users","timestamp":"2022-06-27T17:36:59.358Z"}
{"level":"info","message":"JSON RPC received from peerA, ID: bd57a1cf-7f2f-4ff2-93d4-6727eec4d778, type: request, method: auth","timestamp":"2022-06-27T17:36:59.359Z"}
{"level":"info","message":"JSON RPC received from peerA, ID: 8fd9edc8-6a45-4042-ac06-9ace60567cbf, type: request, method: about","timestamp":"2022-06-27T17:36:59.361Z"}
{"level":"info","message":"JSON RPC received from peerA, ID: 1b27624a-e698-4bbe-9183-b860a1a0d78d, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.363Z"}
{"level":"info","message":"JSON RPC received from peerA, ID: 40fabddf-59c6-4910-a22c-63da7387f99a, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.365Z"}
{"level":"error","message":"Error in authUser(): Login credential do not match","stack":"Error: Login credential do not match\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:144:18)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.421Z"}
{"level":"error","message":"Error in authUser(): login must be specified","stack":"Error: login must be specified\n at AuthRPC.authUser (/home/trout/work/psf/code/ipfs-service-provider/src/controllers/json-rpc/auth/index.js:78:93)\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:165:34)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.423Z"}
{"level":"error","message":"Error in authUser(): password must be specified","stack":"Error: password must be specified\n at AuthRPC.authUser (/home/trout/work/psf/code/ipfs-service-provider/src/controllers/json-rpc/auth/index.js:78:284)\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:185:34)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.425Z"}
{"level":"error","timestamp":"2022-06-27T17:37:04.466Z"}
{"level":"info","message":"Running server in environment: dev","timestamp":"2022-06-27T17:37:04.487Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.495Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.495Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.496Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.497Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.497Z"}
{"level":"error","message":"Error in lib/users.js/getAllUsers()","timestamp":"2022-06-27T17:37:04.499Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.502Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.503Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.503Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.504Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.505Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.506Z"}
{"level":"error","message":"Error in lib/users.js/deleteUser()","timestamp":"2022-06-27T17:37:04.508Z"}
{"level":"info","message":"Running server in environment: test","timestamp":"2022-06-27T17:37:04.514Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.638Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.273Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.277Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.280Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.286Z"}
{"level":"error","timestamp":"2022-06-27T17:37:05.412Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 1","timestamp":"2022-06-27T17:37:05.441Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.448Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.452Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.457Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.463Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.467Z"}
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.473Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.478Z"}
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.478Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 1","timestamp":"2022-06-27T17:37:05.600Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.603Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.615Z"}
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.615Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.619Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.622Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.624Z"}
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.627Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d2a6, Target user: Target Id","timestamp":"2022-06-27T17:37:05.754Z"}
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d2a6","timestamp":"2022-06-27T17:37:05.756Z"}
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.756Z"}
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:06.060Z"}
+45 -7
View File
@@ -2,22 +2,44 @@
Mocks for the Adapter library.
*/
const ipfs = {
ipfsAdapter: {
ipfs: {}
},
ipfsCoordAdapter: {
ipfsCoord: {
class IpfsAdapter {
constructor () {
this.ipfs = {
files: {
stat: () => {}
}
}
}
}
class IpfsCoordAdapter {
constructor () {
this.ipfsCoord = {
useCases: {
peer: {
sendPrivateMessage: () => {}
}
},
thisNode: {
peerData: []
}
},
peerInputHandler: () => {}
this.peerInputHandler = () => {}
this.state = {
serviceProviders: []
}
}
}
const ipfs = {
ipfsAdapter: new IpfsAdapter(),
ipfsCoordAdapter: new IpfsCoordAdapter()
}
ipfs.ipfs = ipfs.ipfsAdapter.ipfs
const localdb = {
Users: class Users {
static findById () {}
@@ -71,6 +93,10 @@ class BchUseCaseMock {
return {}
}
async getUtxosBulk () {
return {}
}
async broadcast () {
return {}
}
@@ -87,6 +113,18 @@ class BchUseCaseMock {
return {}
}
async utxoIsValid() {
return {}
}
async getTokenData() {
return {}
}
async getTokenData2() {
return {}
}
async waitForRPCResponse () {
return {}
}
+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.',
+2
View File
@@ -8,6 +8,8 @@ class IPFSCoord {
}
async start () {}
async subscribeToChat() {}
}
module.exports = IPFSCoord
@@ -48,4 +48,17 @@ describe('#use-cases', () => {
}
})
})
describe('#start', () => {
it('should initialize async use cases', async () => {
const result = await uut.start()
assert.equal(result, true)
})
// it('should catch and throw errors', async () => {
// // Force an error
// sandbox.stub()
// })
})
})
+9 -9
View File
@@ -153,7 +153,7 @@ describe('#users-use-case', () => {
await uut.getAllUsers()
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
// assert.isArray(users)
// assert.isArray(users)
})
it('should catch and throw an error', async () => {
@@ -228,8 +228,8 @@ describe('#users-use-case', () => {
// Assert that the expected properties for the user model exist.
// assert.property(result, 'type')
assert.property(result, '_id')
// assert.property(result, 'email')
// assert.property(result, 'name')
// assert.property(result, 'email')
// assert.property(result, 'name')
})
})
@@ -241,7 +241,7 @@ describe('#users-use-case', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})
@@ -346,7 +346,7 @@ describe('#users-use-case', () => {
// assert.equal(result.name, 'testy tester')
// })
// TODO: verify that an admin can change the type of a user
// TODO: verify that an admin can change the type of a user
})
describe('#authUser', () => {
@@ -356,9 +356,9 @@ describe('#users-use-case', () => {
await uut.authUser('test@test.com', 'password')
// console.log('user: ', user)
// assert.property(user, '_id')
// assert.property(user, 'email')
// assert.property(user, 'name')
// assert.property(user, '_id')
// assert.property(user, 'email')
// assert.property(user, 'name')
})
it('should throw an error if no user matches the login', async () => {
@@ -399,7 +399,7 @@ describe('#users-use-case', () => {
assert.fail('Unexpected code path.')
} catch (err) {
// console.log(err)
assert.include(err.message, 'Cannot read property')
assert.include(err.message, 'Cannot read')
}
})