Skip to content

Commit 84f25ba

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

File tree

10 files changed

+294
-164
lines changed

10 files changed

+294
-164
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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', 'Internal Server Error');
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', 'Internal Server Error');
107+
response.body.should.have.property(
108+
'details',
109+
'KMS returned unexpected response. 502: Unexpected error',
110+
);
111+
});
112+
});

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 & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@ import { DklsDkg, DklsTypes } from '@bitgo-beta/sdk-lib-mpc';
1717
import express from 'express';
1818
import * as t from 'io-ts';
1919

20-
import {
21-
BadRequestResponse,
22-
InternalServerErrorResponse,
23-
NotImplementedResponse,
24-
UnprocessableEntityResponse,
25-
} from '../../shared/errors';
20+
import coinFactory from '../../shared/coinFactory';
21+
import { ErrorResponses, NotImplementedError } from '../../shared/errors';
2622

2723
import { postIndependentKey } from '../../api/advancedWalletManager/handlers/postIndependentKey';
2824
import { recoveryMultisigTransaction } from '../../api/advancedWalletManager/handlers/recoveryMultisigTransaction';
@@ -39,8 +35,6 @@ import { ecdsaMPCv2Finalize } from '../../api/advancedWalletManager/handlers/ecd
3935
import { ecdsaMPCv2Recovery } from '../../api/advancedWalletManager/handlers/ecdsaMPCv2Recovery';
4036
import { signEddsaRecoveryTransaction } from '../../api/advancedWalletManager/handlers/signEddsaRecoveryTransaction';
4137
import { isEddsaCoin } from '../../shared/coinUtils';
42-
import { MethodNotImplementedError } from '@bitgo-beta/sdk-core';
43-
import coinFactory from '../../shared/coinFactory';
4438

4539
// Request type for /key/independent endpoint
4640
const IndependentKeyRequest = {
@@ -52,7 +46,7 @@ const IndependentKeyRequest = {
5246
const IndependentKeyResponse: HttpResponse = {
5347
// TODO: Define proper response type
5448
200: t.any,
55-
...InternalServerErrorResponse,
49+
...ErrorResponses,
5650
};
5751

5852
// Request type for /multisig/sign endpoint
@@ -66,7 +60,7 @@ const SignMultisigRequest = {
6660
const SignMultisigResponse: HttpResponse = {
6761
// TODO: Define proper response type for signed multisig transaction
6862
200: t.any,
69-
...InternalServerErrorResponse,
63+
...ErrorResponses,
7064
};
7165

7266
// Request type for /multisig/recovery endpoint
@@ -83,7 +77,7 @@ const RecoveryMultisigResponse: HttpResponse = {
8377
200: t.type({
8478
txHex: t.string,
8579
}), // the full signed tx
86-
...InternalServerErrorResponse,
80+
...ErrorResponses,
8781
};
8882

8983
const RecoveryMpcRequest = {
@@ -105,7 +99,7 @@ const RecoveryMpcResponse: HttpResponse = {
10599
200: t.type({
106100
txHex: t.string,
107101
}), // the full signed tx
108-
...InternalServerErrorResponse,
102+
...ErrorResponses,
109103
};
110104

111105
// Request type for /mpc/sign endpoint
@@ -163,7 +157,7 @@ const SignMpcResponse: HttpResponse = {
163157
signatureShareRound3: t.any,
164158
}),
165159
]),
166-
...InternalServerErrorResponse,
160+
...ErrorResponses,
167161
};
168162

169163
const KeyShare = {
@@ -373,10 +367,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
373367
}),
374368
response: {
375369
200: t.type(MpcInitializeResponse),
376-
500: t.type({
377-
error: t.string,
378-
details: t.string,
379-
}),
370+
...ErrorResponses,
380371
},
381372
description: 'Initialize MPC for EdDSA key generation',
382373
}),
@@ -414,10 +405,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
414405
}),
415406
response: {
416407
200: MpcFinalizeResponseType,
417-
500: t.type({
418-
error: t.string,
419-
details: t.string,
420-
}),
408+
...ErrorResponses,
421409
},
422410
description: 'Finalize key generation and confirm commonKeychain',
423411
}),
@@ -432,10 +420,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
432420
}),
433421
response: {
434422
200: MpcV2InitializeResponseType,
435-
500: t.type({
436-
error: t.string,
437-
details: t.string,
438-
}),
423+
...ErrorResponses,
439424
},
440425
description: 'Initialize MPC for EdDSA key generation',
441426
}),
@@ -450,9 +435,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
450435
}),
451436
response: {
452437
200: MpcV2RoundResponseType,
453-
...InternalServerErrorResponse,
454-
...BadRequestResponse,
455-
...UnprocessableEntityResponse,
438+
...ErrorResponses,
456439
},
457440
description: 'Perform a round in the MPC protocol',
458441
}),
@@ -467,10 +450,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
467450
}),
468451
response: {
469452
200: MpcV2FinalizeResponseType,
470-
500: t.type({
471-
error: t.string,
472-
details: t.string,
473-
}),
453+
...ErrorResponses,
474454
},
475455
description: 'Finalize the MPC protocol',
476456
}),
@@ -485,9 +465,7 @@ export const AdvancedWalletManagerApiSpec = apiSpec({
485465
}),
486466
response: {
487467
200: MpcV2RecoveryResponseType,
488-
...BadRequestResponse,
489-
...InternalServerErrorResponse,
490-
...NotImplementedResponse,
468+
...ErrorResponses,
491469
},
492470
description: 'Recover a MPC transaction',
493471
}),
@@ -567,7 +545,9 @@ export function createKeyGenRouter(
567545
});
568546
return Response.ok(result);
569547
} else {
570-
throw new MethodNotImplementedError();
548+
throw new NotImplementedError(
549+
`MPC recovery is not implemented for ${coin.getFamily()} coins`,
550+
);
571551
}
572552
}),
573553
]);

0 commit comments

Comments
 (0)