@bitpay-labs/bitcore-wallet-service 11.10.8 → 11.10.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.mocharc.json +6 -0
  2. package/README.md +1 -4
  3. package/mcr.config.js +17 -0
  4. package/package.json +6 -6
  5. package/src/lib/messagebroker.ts +9 -3
  6. package/src/lib/model/notification.ts +2 -2
  7. package/src/lib/routes/helpers/error.ts +1 -0
  8. package/src/lib/routes/tss.ts +51 -4
  9. package/src/lib/server.ts +10 -6
  10. package/src/lib/storage.ts +5 -3
  11. package/src/lib/tss.ts +251 -34
  12. package/test/integration/server.test.ts +2 -2
  13. package/test/messagebroker.test.ts +137 -0
  14. package/test/tss.test.ts +165 -0
  15. package/ts_build/package.json +5 -5
  16. package/ts_build/src/lib/messagebroker.d.ts +4 -2
  17. package/ts_build/src/lib/messagebroker.d.ts.map +1 -1
  18. package/ts_build/src/lib/messagebroker.js +4 -1
  19. package/ts_build/src/lib/messagebroker.js.map +1 -1
  20. package/ts_build/src/lib/model/notification.d.ts +2 -2
  21. package/ts_build/src/lib/model/notification.d.ts.map +1 -1
  22. package/ts_build/src/lib/routes/helpers/error.d.ts.map +1 -1
  23. package/ts_build/src/lib/routes/helpers/error.js +1 -0
  24. package/ts_build/src/lib/routes/helpers/error.js.map +1 -1
  25. package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
  26. package/ts_build/src/lib/routes/tss.js +31 -4
  27. package/ts_build/src/lib/routes/tss.js.map +1 -1
  28. package/ts_build/src/lib/server.d.ts +3 -2
  29. package/ts_build/src/lib/server.d.ts.map +1 -1
  30. package/ts_build/src/lib/server.js +9 -5
  31. package/ts_build/src/lib/server.js.map +1 -1
  32. package/ts_build/src/lib/storage.d.ts +5 -3
  33. package/ts_build/src/lib/storage.d.ts.map +1 -1
  34. package/ts_build/src/lib/storage.js.map +1 -1
  35. package/ts_build/src/lib/tss.d.ts +20 -4
  36. package/ts_build/src/lib/tss.d.ts.map +1 -1
  37. package/ts_build/src/lib/tss.js +155 -17
  38. package/ts_build/src/lib/tss.js.map +1 -1
  39. package/ts_build/test/integration/server.test.js +2 -2
  40. package/ts_build/test/integration/server.test.js.map +1 -1
  41. package/ts_build/test/messagebroker.test.d.ts +2 -0
  42. package/ts_build/test/messagebroker.test.d.ts.map +1 -0
  43. package/ts_build/test/messagebroker.test.js +155 -0
  44. package/ts_build/test/messagebroker.test.js.map +1 -0
  45. package/ts_build/test/tss.test.d.ts +2 -0
  46. package/ts_build/test/tss.test.d.ts.map +1 -0
  47. package/ts_build/test/tss.test.js +190 -0
  48. package/ts_build/test/tss.test.js.map +1 -0
  49. package/.mocharc.yml +0 -2
  50. package/.nycrc.yml +0 -13
package/.mocharc.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "enable-source-maps": true,
3
+ "exit": true,
4
+ "recursive": true,
5
+ "extension": ".test.js"
6
+ }
package/README.md CHANGED
@@ -23,7 +23,7 @@ More about BWS at https://blog.bitpay.com/announcing-the-bitcore-wallet-suite/
23
23
  ```sh
24
24
  git clone https://github.com/bitpay/bitcore.git
25
25
  cd bitcore
26
- npm install
26
+ npm ci
27
27
  npm run bws
28
28
  ```
29
29
 
@@ -86,9 +86,6 @@ There are plenty examples of creating and sending proposals in the `/test/integr
86
86
  - copay running on port: 8100
87
87
  - bitcoin-core running on regtest mode (blue icon logo)
88
88
 
89
- > mongo topology crashes sometimes due to notifications being incompatible in a web browser
90
- > **bitcore-wallet-service/lib/notificationbroadcaster.js**
91
- > Note: If testing on a PC browser, comment out notificationbroadcaster.js to disable notifications.
92
89
 
93
90
  ### Steps:
94
91
 
package/mcr.config.js ADDED
@@ -0,0 +1,17 @@
1
+ // @ts-check
2
+ /* eslint-disable */
3
+ const builder = require('../../coverage-builder.js').default;
4
+
5
+
6
+ /**
7
+ * @type {import('monocart-coverage-reports').CoverageReportOptions}
8
+ */
9
+ const config = builder({
10
+ bytes: 70,
11
+ statements: 70,
12
+ branches: 60,
13
+ functions: 60,
14
+ lines: 70
15
+ });
16
+
17
+ module.exports = config;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@bitpay-labs/bitcore-wallet-service",
3
3
  "description": "A service for Mutisig HD Bitcoin Wallets",
4
4
  "author": "BitPay Inc",
5
- "version": "11.10.8",
5
+ "version": "11.10.10",
6
6
  "license": "MIT",
7
7
  "main": "ts_build/src/index.js",
8
8
  "types": "./ts_build/src/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "scripts": {
25
25
  "start": "npm run clean && npm run build && ./start.sh",
26
26
  "stop": "./stop.sh",
27
- "test": "npm run compile && nyc mocha 'ts_build/test/**/*.test.js'",
27
+ "test": "npm run compile && mcr mocha ts_build/test",
28
28
  "build": "tsc && npm run cp:templates",
29
29
  "build:prod": "tsc -p tsconfig.prod.json && npm run cp:templates",
30
30
  "build:docker": "docker build -t bitpay/bitcore-wallet-service:latest -f ./Dockerfile ../..",
@@ -41,9 +41,9 @@
41
41
  "cp:templates": "cp -r ./templates ./ts_build/templates"
42
42
  },
43
43
  "dependencies": {
44
- "@bitpay-labs/bitcore-logging": "^11.10.4",
45
- "@bitpay-labs/bitcore-tss": "^11.10.8",
46
- "@bitpay-labs/crypto-wallet-core": "^11.10.4",
44
+ "@bitpay-labs/bitcore-logging": "^11.10.9",
45
+ "@bitpay-labs/bitcore-tss": "^11.10.9",
46
+ "@bitpay-labs/crypto-wallet-core": "^11.10.9",
47
47
  "@sendgrid/mail": "6.5.4",
48
48
  "abi-decoder": "2.4.0",
49
49
  "async": "0.9.2",
@@ -107,5 +107,5 @@
107
107
  "email": "kjoseph@bitpay.com"
108
108
  }
109
109
  ],
110
- "gitHead": "c22ec6ab93ac3d455bfd76421509ecfa7b0aab1c"
110
+ "gitHead": "ea86af5f3e1049d0ee1469fd0cd1fa34c2a579c4"
111
111
  }
@@ -2,10 +2,12 @@ import { EventEmitter } from 'events';
2
2
  import * as io from 'socket.io-client';
3
3
  import 'source-map-support/register';
4
4
  import logger from './logger';
5
+ import type { Notification } from './model/notification';
5
6
 
6
7
  export class MessageBroker extends EventEmitter {
7
- remote: boolean;
8
+ remote: boolean = false;
8
9
  mq: io.Socket;
10
+
9
11
  constructor(opts) {
10
12
  super();
11
13
 
@@ -28,7 +30,7 @@ export class MessageBroker extends EventEmitter {
28
30
  }
29
31
  }
30
32
 
31
- send(data) {
33
+ send(data: Notification) {
32
34
  if (this.remote) {
33
35
  this.mq.emit('msg', data);
34
36
  } else {
@@ -36,7 +38,11 @@ export class MessageBroker extends EventEmitter {
36
38
  }
37
39
  }
38
40
 
39
- onMessage(handler) {
41
+ onMessage(handler: (data: Notification) => void) {
40
42
  this.on('msg', handler);
41
43
  }
44
+
45
+ offMessage(handler: (data: Notification) => void) {
46
+ this.off('msg', handler);
47
+ }
42
48
  }
@@ -24,7 +24,7 @@
24
24
  export interface INotification {
25
25
  version: string;
26
26
  createdOn: number;
27
- id: number;
27
+ id: string | number;
28
28
  type: string;
29
29
  data: any;
30
30
  walletId: string;
@@ -32,7 +32,7 @@ export interface INotification {
32
32
  isCreator: boolean;
33
33
  }
34
34
 
35
- export class Notification {
35
+ export class Notification implements INotification {
36
36
  version: string;
37
37
  createdOn: number;
38
38
  id: string | number;
@@ -13,6 +13,7 @@ export class ApiErrorHelper {
13
13
  returnError(err: any, res: express.Response, req: express.Request): void {
14
14
  // make sure headers have not been sent as this leads to an uncaught error
15
15
  if (res.headersSent) {
16
+ res.end();
16
17
  return;
17
18
  }
18
19
  if (err instanceof ClientError) {
@@ -37,17 +37,40 @@ export class TssRouter {
37
37
  });
38
38
 
39
39
  router.get('/v1/tss/keygen/:id/:round', authTssRequest(), async function(req, res) {
40
+ let interval: NodeJS.Timeout;
40
41
  try {
42
+ const abort = new AbortController();
43
+ res.on('close', () => { clearInterval(interval); abort.abort(); });
44
+
41
45
  const { id, round } = req.params as { [key: string]: string };
46
+ const { maxWaitTime } = req.query as { [key: string]: string };
42
47
  const copayerId = req.headers['x-identity'];
43
48
  if (round === 'secret') {
44
49
  const secret = await TssKeyGen.getBwsJoinSecret({ id, copayerId });
45
50
  return res.json({ secret });
46
51
  }
47
- const { messages, publicKey } = await TssKeyGen.getMessagesForParty({ id, round: parseInt(round), copayerId });
48
- return res.json({ messages, publicKey });
52
+
53
+ // Validate access and fetch session before committing to a streaming response, so that errors like
54
+ // "session not found" or "not a participant" can still be returned with a proper
55
+ // HTTP status instead of silently becoming an empty 200 (see below).
56
+ const session = await TssKeyGen.getSessionForCopayer({ id, copayerId });
57
+
58
+ if (abort.signal.aborted || res.closed) {
59
+ return; // Connection closed by client, stop processing
60
+ }
61
+
62
+ // Keep the connection alive while waiting for the change stream to return a result.
63
+ // Headers must be finalized before writing the first heartbeat byte.
64
+ // Flush ensures the heartbeat is sent immediately to keep the connection alive.
65
+ res.writeHead(200, { 'Content-Type': 'application/json' });
66
+ interval = setInterval(() => { res.write('\n'); res.flush(); }, 1000);
67
+
68
+ const { messages, publicKey } = await TssKeyGen.getMessagesForParty({ session, round: parseInt(round), copayerId, maxWaitTimeSec: parseInt(maxWaitTime), abortSignal: abort.signal });
69
+ return res.end(JSON.stringify({ messages, publicKey }));
49
70
  } catch (err) {
50
71
  return returnError(err ?? 'unknown', res, req);
72
+ } finally {
73
+ clearInterval(interval);
51
74
  }
52
75
  });
53
76
 
@@ -104,13 +127,37 @@ export class TssRouter {
104
127
  });
105
128
 
106
129
  router.get('/v1/tss/sign/:id/:round', authTssRequest(), async function(req, res) {
130
+ let interval: NodeJS.Timeout;
107
131
  try {
132
+ const abort = new AbortController();
133
+ res.on('close', () => { clearInterval(interval); abort.abort(); });
134
+
108
135
  const { id, round } = req.params as { [key: string]: string };
136
+ const { maxWaitTime } = req.query as { [key: string]: string };
109
137
  const copayerId = req.headers['x-identity'];
110
- const { messages, signature, participants } = await TssSign.getMessagesForParty({ id, round: parseInt(round), copayerId });
111
- return res.json({ messages, signature, participants });
138
+
139
+ // Validate access and fetch session before committing to a streaming response, so that errors like
140
+ // "session not found" or "not a participant" can still be returned with a proper
141
+ // HTTP status instead of silently becoming an empty 200 (see below).
142
+ const session = await TssSign.getSessionForCopayer({ id, copayerId });
143
+
144
+ if (abort.signal.aborted || res.closed) {
145
+ return; // Connection closed by client, stop processing
146
+ }
147
+
148
+ // Keep the connection alive while waiting for the change stream to return a result.
149
+ // Headers must be finalized before writing the first heartbeat byte.
150
+ // Flush ensures the heartbeat is sent immediately to keep the connection alive.
151
+ res.writeHead(200, { 'Content-Type': 'application/json' });
152
+ interval = setInterval(() => { res.write('\n'); res.flush(); }, 1000);
153
+
154
+ const { messages, signature, participants } = await TssSign.getMessagesForParty({ session, round: parseInt(round), copayerId, maxWaitTimeSec: parseInt(maxWaitTime), abortSignal: abort.signal });
155
+ clearInterval(interval);
156
+ return res.end(JSON.stringify({ messages, signature, participants }));
112
157
  } catch (err) {
113
158
  return returnError(err ?? 'unknown', res, req);
159
+ } finally {
160
+ clearInterval(interval);
114
161
  }
115
162
  });
116
163
 
package/src/lib/server.ts CHANGED
@@ -326,12 +326,9 @@ export class WalletService implements IWalletService {
326
326
  );
327
327
  }
328
328
 
329
- static handleIncomingNotifications(notification, cb) {
330
- cb = cb || function() { };
331
-
329
+ static handleIncomingNotifications(_notification: INotification) {
332
330
  // do nothing here....
333
331
  // bc height cache is cleared on bcmonitor
334
- return cb();
335
332
  }
336
333
 
337
334
  static shutDown(cb) {
@@ -474,6 +471,13 @@ export class WalletService implements IWalletService {
474
471
  return storage;
475
472
  }
476
473
 
474
+ static getMessageBroker() {
475
+ if (!initialized) {
476
+ throw new Error('Message broker requested before server was initialized');
477
+ }
478
+ return messageBroker;
479
+ }
480
+
477
481
  _runLocked(cb, task, waitTime?: number) {
478
482
  $.checkState(this.walletId, 'Failed state: this.walletId undefined at <_runLocked()>');
479
483
  this.lock.runLocked(this.walletId, { waitTime }, cb, task);
@@ -1152,7 +1156,7 @@ export class WalletService implements IWalletService {
1152
1156
  try {
1153
1157
  const isValid = this._verifyRequestPubKey(opts.requestPubKey, opts.signature, target.xPubKey);
1154
1158
  if (!isValid) return cb(Errors.NOT_AUTHORIZED);
1155
- } catch (e) {
1159
+ } catch {
1156
1160
  return cb(Errors.NOT_AUTHORIZED);
1157
1161
  }
1158
1162
 
@@ -3857,7 +3861,7 @@ export class WalletService implements IWalletService {
3857
3861
  const notifications = res
3858
3862
  .flat()
3859
3863
  .map((n: INotification) => ({ ...n, walletId: this.walletId }))
3860
- .sort((a, b) => a.id - b.id);
3864
+ .sort((a, b) => a.id?.toString()?.localeCompare(b.id?.toString()));
3861
3865
 
3862
3866
  return cb(null, notifications);
3863
3867
  }
@@ -1,6 +1,5 @@
1
1
  import * as async from 'async';
2
2
  import _ from 'lodash';
3
- import { Db } from 'mongodb';
4
3
  import * as mongodb from 'mongodb';
5
4
  import preconditions from 'preconditions';
6
5
  import { BCHAddressTranslator } from './bchaddresstranslator'; // only for migration
@@ -19,8 +18,11 @@ import {
19
18
  TxProposal,
20
19
  Wallet
21
20
  } from './model';
22
- import { ITssKeyMessageObject, TssKeyGenModel } from './model/tsskeygen';
23
- import { ITssSigMessageObject, TssSigGenModel } from './model/tsssign';
21
+ import { TssKeyGenModel } from './model/tsskeygen';
22
+ import { TssSigGenModel } from './model/tsssign';
23
+ import type { ITssKeyMessageObject } from './model/tsskeygen';
24
+ import type { ITssSigMessageObject } from './model/tsssign';
25
+ import type { Db } from 'mongodb';
24
26
 
25
27
  const $ = preconditions.singleton();
26
28