Skip to content

Commit ef520da

Browse files
committed
fix(root): add retry mechanism for MPC sendSignatureShareV2
added a retry mechanism with a backoff to sendSignatureShareV2 for MPC when the response is a 429 rate limit error WP-3392 TICKET: WP-3392
1 parent e28a48b commit ef520da

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-4
lines changed

modules/bitgo/test/v2/unit/internal/tssUtils/ecdsaMPCv2/signTxRequest.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ describe('signTxRequest:', function () {
123123
},
124124
],
125125
curve: 'secp256k1',
126+
config: {
127+
rejectCurves: new Set(),
128+
},
126129
});
127130
const constants = {
128131
mpc: {
@@ -238,6 +241,47 @@ describe('signTxRequest:', function () {
238241
nockPromises[1].isDone().should.be.true();
239242
nockPromises[2].isDone().should.be.true();
240243
});
244+
245+
it('successfully signs a txRequest for a dkls hot wallet after receiving multiple 429 errors', async function () {
246+
const nockPromises = [
247+
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey),
248+
await nockTxRequestResponseSignatureShareRoundTwo(bitgoParty, txRequest, bitgoGpgKey, 0, 3),
249+
await nockTxRequestResponseSignatureShareRoundThree(txRequest),
250+
await nockSendTxRequest(txRequest),
251+
];
252+
await Promise.all(nockPromises);
253+
254+
const userShare = fs.readFileSync(shareFiles[vector.party1]);
255+
const userPrvBase64 = Buffer.from(userShare).toString('base64');
256+
await tssUtils.signTxRequest({
257+
txRequest,
258+
prv: userPrvBase64,
259+
reqId,
260+
});
261+
nockPromises[0].isDone().should.be.true();
262+
nockPromises[1].isDone().should.be.true();
263+
nockPromises[2].isDone().should.be.true();
264+
});
265+
266+
it('fails to signs a txRequest for a dkls hot wallet after receiving over 3 429 errors', async function () {
267+
const nockPromises = [
268+
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey),
269+
await nockTxRequestResponseSignatureShareRoundTwo(bitgoParty, txRequest, bitgoGpgKey, 0, 4),
270+
];
271+
await Promise.all(nockPromises);
272+
273+
const userShare = fs.readFileSync(shareFiles[vector.party1]);
274+
const userPrvBase64 = Buffer.from(userShare).toString('base64');
275+
await tssUtils
276+
.signTxRequest({
277+
txRequest,
278+
prv: userPrvBase64,
279+
reqId,
280+
})
281+
.should.be.rejectedWith('Too many requests, slow down!');
282+
nockPromises[0].isDone().should.be.true();
283+
nockPromises[1].isDone().should.be.false();
284+
});
241285
});
242286

243287
export function getBitGoPartyGpgKeyPrv(key: openpgp.SerializedKeyPair<string>): DklsTypes.PartyGpgKey {
@@ -336,11 +380,27 @@ async function nockTxRequestResponseSignatureShareRoundTwo(
336380
bitgoSession: DklsDsg.Dsg,
337381
txRequest: TxRequest,
338382
bitgoGpgKey: openpgp.SerializedKeyPair<string>,
339-
partyId: 0 | 1 = 0
383+
partyId: 0 | 1 = 0,
384+
rateLimitErrorCount = 0
340385
): Promise<nock.Scope> {
341386
const transactions = getRoute('ecdsa');
342-
return nock('https://bitgo.fakeurl')
343-
.persist(true)
387+
const scope = nock('https://bitgo.fakeurl');
388+
389+
if (rateLimitErrorCount > 0) {
390+
scope
391+
.post(
392+
`/api/v2/wallet/${txRequest.walletId}/txrequests/${txRequest.txRequestId + transactions}/sign`,
393+
(body) => (JSON.parse(body.signatureShares[0].share) as MPCv2SignatureShareRound2Input).type === 'round2Input'
394+
)
395+
.times(rateLimitErrorCount)
396+
.reply(429, {
397+
error: 'Too many requests, slow down!',
398+
name: 'TooManyRequests',
399+
requestId: 'cm5qx01lh0013b2ek2sxl4w00',
400+
context: {},
401+
});
402+
}
403+
return scope
344404
.post(
345405
`/api/v2/wallet/${txRequest.walletId}/txrequests/${txRequest.txRequestId + transactions}/sign`,
346406
(body) => (JSON.parse(body.signatureShares[0].share) as MPCv2SignatureShareRound2Input).type === 'round2Input'

modules/sdk-core/src/bitgo/tss/common.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
} from '../utils';
1616
import { IRequestTracer } from '../../api';
1717

18+
const debug = require('debug')('bitgo:tss:common');
19+
1820
/**
1921
* Gets the latest Tx Request by id
2022
*
@@ -140,7 +142,26 @@ export async function sendSignatureShareV2(
140142
};
141143
const reqTracer = reqId || new RequestTracer();
142144
bitgo.setRequestTracer(reqTracer);
143-
return bitgo.post(bitgo.url(urlPath, 2)).send(requestBody).result();
145+
146+
let attempts = 0;
147+
const maxAttempts = 3;
148+
149+
while (attempts < maxAttempts) {
150+
try {
151+
return await bitgo.post(bitgo.url(urlPath, 2)).send(requestBody).result();
152+
} catch (err) {
153+
if (err?.status === 429) {
154+
const sleepTime = 1000 * (attempts + 1);
155+
debug(`MPC Signing rate limit error - retrying in ${sleepTime / 1000} seconds`);
156+
// sleep for a bit before retrying
157+
await new Promise((resolve) => setTimeout(resolve, sleepTime));
158+
attempts++;
159+
} else {
160+
throw err;
161+
}
162+
}
163+
}
164+
return await bitgo.post(bitgo.url(urlPath, 2)).send(requestBody).result();
144165
}
145166

146167
/**

0 commit comments

Comments
 (0)