Skip to content

Commit b9cadf0

Browse files
committed
feat(ebe, mbe): added error handling between expresses and kms
Ticket: WP-5241
1 parent 462f455 commit b9cadf0

File tree

10 files changed

+292
-159
lines changed

10 files changed

+292
-159
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { AppMode, AdvancedWalletManagerConfig, TlsMode } from '../../../initConfig';
2+
import { app as expressApp } from '../../../advancedWalletManagerApp';
3+
4+
import express from 'express';
5+
import nock from 'nock';
6+
import 'should';
7+
import * as request from 'supertest';
8+
9+
describe('postMpcV2Key', () => {
10+
let cfg: AdvancedWalletManagerConfig;
11+
let app: express.Application;
12+
let agent: request.SuperAgentTest;
13+
14+
// test config
15+
const kmsUrl = 'http://kms.invalid';
16+
const coin = 'tsol';
17+
const accessToken = 'test-token';
18+
19+
before(() => {
20+
// nock config
21+
nock.disableNetConnect();
22+
nock.enableNetConnect('127.0.0.1');
23+
24+
// app config
25+
cfg = {
26+
appMode: AppMode.ADVANCED_WALLET_MANAGER,
27+
port: 0, // Let OS assign a free port
28+
bind: 'localhost',
29+
timeout: 60000,
30+
httpLoggerFile: '',
31+
kmsUrl: kmsUrl,
32+
tlsMode: TlsMode.DISABLED,
33+
allowSelfSigned: true,
34+
};
35+
36+
// app setup
37+
app = expressApp(cfg);
38+
agent = request.agent(app);
39+
});
40+
41+
afterEach(() => {
42+
nock.cleanAll();
43+
});
44+
45+
it('should bubble up 400 KMS errors', async () => {
46+
nock(kmsUrl).post(/.*/).reply(400, { message: 'This is an error message' }).persist();
47+
48+
const response = await agent
49+
.post(`/api/${coin}/mpcv2/initialize`)
50+
.set('Authorization', `Bearer ${accessToken}`)
51+
.send({ source: 'user' });
52+
53+
response.status.should.equal(400);
54+
response.body.should.have.property('error', 'BadRequestError');
55+
response.body.should.have.property('details', 'This is an error message');
56+
});
57+
58+
it('should bubble up 404 KMS errors', async () => {
59+
nock(kmsUrl).post(/.*/).reply(404, { message: 'This is an error message' }).persist();
60+
61+
const response = await agent
62+
.post(`/api/${coin}/mpcv2/initialize`)
63+
.set('Authorization', `Bearer ${accessToken}`)
64+
.send({ source: 'user' });
65+
66+
response.status.should.equal(404);
67+
response.body.should.have.property('error', 'NotFoundError');
68+
response.body.should.have.property('details', 'This is an error message');
69+
});
70+
71+
it('should bubble up 409 KMS errors', async () => {
72+
nock(kmsUrl).post(/.*/).reply(409, { message: 'This is an error message' }).persist();
73+
74+
const response = await agent
75+
.post(`/api/${coin}/mpcv2/initialize`)
76+
.set('Authorization', `Bearer ${accessToken}`)
77+
.send({ source: 'user' });
78+
79+
response.status.should.equal(409);
80+
response.body.should.have.property('error', 'ConflictError');
81+
response.body.should.have.property('details', 'This is an error message');
82+
});
83+
84+
it('should bubble up 500 KMS errors', async () => {
85+
nock(kmsUrl).post(/.*/).reply(500, { message: 'This is an error message' }).persist();
86+
87+
const response = await agent
88+
.post(`/api/${coin}/mpcv2/initialize`)
89+
.set('Authorization', `Bearer ${accessToken}`)
90+
.send({ source: 'user' });
91+
92+
response.status.should.equal(500);
93+
response.body.should.have.property('error', 'InternalServerError');
94+
response.body.should.have.property('details', 'This is an error message');
95+
});
96+
97+
it('should handle unexpected KMS errors', async () => {
98+
nock(kmsUrl).post(/.*/).reply(502, { message: 'Unexpected error' }).persist();
99+
100+
const response = await agent
101+
.post(`/api/${coin}/mpcv2/initialize`)
102+
.set('Authorization', `Bearer ${accessToken}`)
103+
.send({ source: 'user' });
104+
105+
response.status.should.equal(500);
106+
response.body.should.have.property('error', 'InternalServerError');
107+
response.body.should.have.property('details', 'KMS returned unexpected response. 502: Unexpected error');
108+
});
109+
110+
});

src/__tests__/api/advancedWalletManager/recoveryMpcV2.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ describe('recoveryMpcV2', async () => {
155155

156156
signatureResponse.status.should.equal(400);
157157
signatureResponse.body.should.have.property('error');
158-
signatureResponse.body.error.should.startWith(
158+
signatureResponse.body.error.should.equal('BadRequestError');
159+
signatureResponse.body.should.have.property('details');
160+
signatureResponse.body.details.should.startWith(
159161
'Failed to construct eth transaction from message hex',
160162
);
161163
});

src/__tests__/api/master/recoveryWalletMpcV2.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ describe('MBE mpcv2 recovery', () => {
202202

203203
response.status.should.equal(422);
204204
response.body.should.have.property('error');
205-
response.body.error.should.equal(
205+
response.body.error.should.equal('ValidationError');
206+
response.body.should.have.property('details');
207+
response.body.details.should.equal(
206208
'ECDSA ETH-like recovery specific parameters are required for MPC recovery',
207209
);
208210
});

src/__tests__/api/master/sendMany.test.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as request from 'supertest';
55
import nock from 'nock';
66
import { app as expressApp } from '../../../masterBitGoExpressApp';
77
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
8-
import { ApiResponseError, Environments, Wallet } from '@bitgo-beta/sdk-core';
8+
import { Environments, Wallet } from '@bitgo-beta/sdk-core';
99
import { Tbtc } from '@bitgo-beta/sdk-coin-btc';
1010
import assert from 'assert';
1111

@@ -747,15 +747,11 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => {
747747

748748
const verifyStub = sinon.stub(Tbtc.prototype, 'verifyTransaction').resolves(true);
749749

750-
// Mock advanced wallet manager sign request to return an error
751-
const signNock = nock(advancedWalletManagerUrl)
752-
.post(`/api/${coin}/multisig/sign`)
753-
.replyWithError(
754-
new ApiResponseError('Custom API error', 500, {
755-
error: 'Custom API error',
756-
requestId: 'test-request-id',
757-
}),
758-
);
750+
// Mock enclaved express sign request to return an error
751+
const signNock = nock(advancedWalletManagerUrl).post(`/api/${coin}/multisig/sign`).reply(500, {
752+
error: 'Internal Server Error',
753+
details: 'Custom API error details',
754+
});
759755

760756
const response = await agent
761757
.post(`/api/${coin}/wallet/${walletId}/sendMany`)
@@ -775,11 +771,8 @@ describe('POST /api/:coin/wallet/:walletId/sendmany', () => {
775771
response.status.should.equal(500);
776772
response.body.should.have.property('error');
777773
response.body.should.have.property('details');
778-
response.body.error.should.equal('BitGoApiResponseError');
779-
response.body.details.should.deepEqual({
780-
error: 'Custom API error',
781-
requestId: 'test-request-id',
782-
});
774+
response.body.error.should.equal('Internal Server Error');
775+
response.body.details.should.deepEqual('Custom API error details');
783776

784777
walletGetNock.done();
785778
keychainGetNock.done();

src/advancedWalletManager/routers/advancedWalletManagerApiSpec.ts

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
NotImplementedResponse,
2424
UnprocessableEntityResponse,
2525
} from '../../shared/errors';
26+
import coinFactory from '../../shared/coinFactory';
27+
import { ErrorResponses, NotImplementedError } from '../../shared/errors';
2628

2729
import { postIndependentKey } from '../../api/advancedWalletManager/handlers/postIndependentKey';
2830
import { recoveryMultisigTransaction } from '../../api/advancedWalletManager/handlers/recoveryMultisigTransaction';
@@ -39,8 +41,6 @@ import { ecdsaMPCv2Finalize } from '../../api/advancedWalletManager/handlers/ecd
3941
import { ecdsaMPCv2Recovery } from '../../api/advancedWalletManager/handlers/ecdsaMPCv2Recovery';
4042
import { signEddsaRecoveryTransaction } from '../../api/advancedWalletManager/handlers/signEddsaRecoveryTransaction';
4143
import { isEddsaCoin } from '../../shared/coinUtils';
42-
import { MethodNotImplementedError } from '@bitgo-beta/sdk-core';
43-
import coinFactory from '../../shared/coinFactory';
4444

4545
// Request type for /key/independent endpoint
4646
const IndependentKeyRequest = {
@@ -52,7 +52,7 @@ const IndependentKeyRequest = {
5252
const IndependentKeyResponse: HttpResponse = {
5353
// TODO: Define proper response type
5454
200: t.any,
55-
...InternalServerErrorResponse,
55+
...ErrorResponses,
5656
};
5757

5858
// Request type for /multisig/sign endpoint
@@ -66,7 +66,7 @@ const SignMultisigRequest = {
6666
const SignMultisigResponse: HttpResponse = {
6767
// TODO: Define proper response type for signed multisig transaction
6868
200: t.any,
69-
...InternalServerErrorResponse,
69+
...ErrorResponses,
7070
};
7171

7272
// Request type for /multisig/recovery endpoint
@@ -83,7 +83,7 @@ const RecoveryMultisigResponse: HttpResponse = {
8383
200: t.type({
8484
txHex: t.string,
8585
}), // the full signed tx
86-
...InternalServerErrorResponse,
86+
...ErrorResponses,
8787
};
8888

8989
const RecoveryMpcRequest = {
@@ -105,7 +105,7 @@ const RecoveryMpcResponse: HttpResponse = {
105105
200: t.type({
106106
txHex: t.string,
107107
}), // the full signed tx
108-
...InternalServerErrorResponse,
108+
...ErrorResponses,
109109
};
110110

111111
// Request type for /mpc/sign endpoint
@@ -163,7 +163,7 @@ const SignMpcResponse: HttpResponse = {
163163
signatureShareRound3: t.any,
164164
}),
165165
]),
166-
...InternalServerErrorResponse,
166+
...ErrorResponses,
167167
};
168168

169169
const KeyShare = {
@@ -373,10 +373,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
373373
}),
374374
response: {
375375
200: t.type(MpcInitializeResponse),
376-
500: t.type({
377-
error: t.string,
378-
details: t.string,
379-
}),
376+
...ErrorResponses,
380377
},
381378
description: 'Initialize MPC for EdDSA key generation',
382379
}),
@@ -414,10 +411,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
414411
}),
415412
response: {
416413
200: MpcFinalizeResponseType,
417-
500: t.type({
418-
error: t.string,
419-
details: t.string,
420-
}),
414+
...ErrorResponses,
421415
},
422416
description: 'Finalize key generation and confirm commonKeychain',
423417
}),
@@ -432,10 +426,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
432426
}),
433427
response: {
434428
200: MpcV2InitializeResponseType,
435-
500: t.type({
436-
error: t.string,
437-
details: t.string,
438-
}),
429+
...ErrorResponses,
439430
},
440431
description: 'Initialize MPC for EdDSA key generation',
441432
}),
@@ -450,9 +441,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
450441
}),
451442
response: {
452443
200: MpcV2RoundResponseType,
453-
...InternalServerErrorResponse,
454-
...BadRequestResponse,
455-
...UnprocessableEntityResponse,
444+
...ErrorResponses,
456445
},
457446
description: 'Perform a round in the MPC protocol',
458447
}),
@@ -467,10 +456,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
467456
}),
468457
response: {
469458
200: MpcV2FinalizeResponseType,
470-
500: t.type({
471-
error: t.string,
472-
details: t.string,
473-
}),
459+
...ErrorResponses,
474460
},
475461
description: 'Finalize the MPC protocol',
476462
}),
@@ -485,9 +471,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
485471
}),
486472
response: {
487473
200: MpcV2RecoveryResponseType,
488-
...BadRequestResponse,
489-
...InternalServerErrorResponse,
490-
...NotImplementedResponse,
474+
...ErrorResponses,
491475
},
492476
description: 'Recover a MPC transaction',
493477
}),
@@ -567,7 +551,9 @@ export function createKeyGenRouter(
567551
});
568552
return Response.ok(result);
569553
} else {
570-
throw new MethodNotImplementedError();
554+
throw new NotImplementedError(
555+
`MPC recovery is not implemented for ${coin.getFamily()} coins`,
556+
);
571557
}
572558
}),
573559
]);

0 commit comments

Comments
 (0)