Skip to content

Commit

Permalink
fix(root): add retry mechanism for MPC sendSignatureShareV2
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alebusse committed Jan 10, 2025
1 parent e28a48b commit ef520da
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ describe('signTxRequest:', function () {
},
],
curve: 'secp256k1',
config: {
rejectCurves: new Set(),
},
});
const constants = {
mpc: {
Expand Down Expand Up @@ -238,6 +241,47 @@ describe('signTxRequest:', function () {
nockPromises[1].isDone().should.be.true();
nockPromises[2].isDone().should.be.true();
});

it('successfully signs a txRequest for a dkls hot wallet after receiving multiple 429 errors', async function () {
const nockPromises = [
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey),
await nockTxRequestResponseSignatureShareRoundTwo(bitgoParty, txRequest, bitgoGpgKey, 0, 3),
await nockTxRequestResponseSignatureShareRoundThree(txRequest),
await nockSendTxRequest(txRequest),
];
await Promise.all(nockPromises);

const userShare = fs.readFileSync(shareFiles[vector.party1]);
const userPrvBase64 = Buffer.from(userShare).toString('base64');
await tssUtils.signTxRequest({
txRequest,
prv: userPrvBase64,
reqId,
});
nockPromises[0].isDone().should.be.true();
nockPromises[1].isDone().should.be.true();
nockPromises[2].isDone().should.be.true();
});

it('fails to signs a txRequest for a dkls hot wallet after receiving over 3 429 errors', async function () {
const nockPromises = [
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey),
await nockTxRequestResponseSignatureShareRoundTwo(bitgoParty, txRequest, bitgoGpgKey, 0, 4),
];
await Promise.all(nockPromises);

const userShare = fs.readFileSync(shareFiles[vector.party1]);
const userPrvBase64 = Buffer.from(userShare).toString('base64');
await tssUtils
.signTxRequest({
txRequest,
prv: userPrvBase64,
reqId,
})
.should.be.rejectedWith('Too many requests, slow down!');
nockPromises[0].isDone().should.be.true();
nockPromises[1].isDone().should.be.false();
});
});

export function getBitGoPartyGpgKeyPrv(key: openpgp.SerializedKeyPair<string>): DklsTypes.PartyGpgKey {
Expand Down Expand Up @@ -336,11 +380,27 @@ async function nockTxRequestResponseSignatureShareRoundTwo(
bitgoSession: DklsDsg.Dsg,
txRequest: TxRequest,
bitgoGpgKey: openpgp.SerializedKeyPair<string>,
partyId: 0 | 1 = 0
partyId: 0 | 1 = 0,
rateLimitErrorCount = 0
): Promise<nock.Scope> {
const transactions = getRoute('ecdsa');
return nock('https://bitgo.fakeurl')
.persist(true)
const scope = nock('https://bitgo.fakeurl');

if (rateLimitErrorCount > 0) {
scope
.post(
`/api/v2/wallet/${txRequest.walletId}/txrequests/${txRequest.txRequestId + transactions}/sign`,
(body) => (JSON.parse(body.signatureShares[0].share) as MPCv2SignatureShareRound2Input).type === 'round2Input'
)
.times(rateLimitErrorCount)
.reply(429, {
error: 'Too many requests, slow down!',
name: 'TooManyRequests',
requestId: 'cm5qx01lh0013b2ek2sxl4w00',
context: {},
});
}
return scope
.post(
`/api/v2/wallet/${txRequest.walletId}/txrequests/${txRequest.txRequestId + transactions}/sign`,
(body) => (JSON.parse(body.signatureShares[0].share) as MPCv2SignatureShareRound2Input).type === 'round2Input'
Expand Down
23 changes: 22 additions & 1 deletion modules/sdk-core/src/bitgo/tss/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
} from '../utils';
import { IRequestTracer } from '../../api';

const debug = require('debug')('bitgo:tss:common');

/**
* Gets the latest Tx Request by id
*
Expand Down Expand Up @@ -140,7 +142,26 @@ export async function sendSignatureShareV2(
};
const reqTracer = reqId || new RequestTracer();
bitgo.setRequestTracer(reqTracer);
return bitgo.post(bitgo.url(urlPath, 2)).send(requestBody).result();

let attempts = 0;
const maxAttempts = 3;

while (attempts < maxAttempts) {
try {
return await bitgo.post(bitgo.url(urlPath, 2)).send(requestBody).result();
} catch (err) {
if (err?.status === 429) {
const sleepTime = 1000 * (attempts + 1);
debug(`MPC Signing rate limit error - retrying in ${sleepTime / 1000} seconds`);
// sleep for a bit before retrying
await new Promise((resolve) => setTimeout(resolve, sleepTime));
attempts++;
} else {
throw err;
}
}
}
return await bitgo.post(bitgo.url(urlPath, 2)).send(requestBody).result();
}

/**
Expand Down

0 comments on commit ef520da

Please sign in to comment.