Skip to content

Commit 211b367

Browse files
authored
Merge pull request #200 from BitGo/pranavjain/wcn-447-fix-wallet-pubs-consolidate-accelerate
fix(master-express): forward walletPubs in consolidateUnspents and accelerate
2 parents eb0a27c + 4682109 commit 211b367

8 files changed

Lines changed: 555 additions & 16 deletions

File tree

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

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => {
3434
type: 'independent',
3535
};
3636

37+
const mockBitgoKeychain = {
38+
id: 'bitgo-key-id',
39+
pub: 'xpub661MyMwAqRbcHtYNxRNuEtDFmPMRzBVPDfBXNu2RUBVFNz8MnWQgkrMZCNB',
40+
type: 'bitgo',
41+
};
42+
3743
before(() => {
3844
nock.disableNetConnect();
3945
nock.enableNetConnect('127.0.0.1');
@@ -68,11 +74,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => {
6874
.matchHeader('authorization', `Bearer ${accessToken}`)
6975
.reply(200, mockWalletData);
7076

77+
// Signing keychain fetched by getWalletAndSigningKeychain
7178
const keychainGetNock = nock(bitgoApiUrl)
7279
.get(`/api/v2/${coin}/key/user-key-id`)
7380
.matchHeader('authorization', `Bearer ${accessToken}`)
7481
.reply(200, mockUserKeychain);
7582

83+
// All 3 keychains fetched for walletPubs
84+
nock(bitgoApiUrl)
85+
.get(`/api/v2/${coin}/key/user-key-id`)
86+
.matchHeader('authorization', `Bearer ${accessToken}`)
87+
.reply(200, mockUserKeychain);
88+
nock(bitgoApiUrl)
89+
.get(`/api/v2/${coin}/key/backup-key-id`)
90+
.matchHeader('authorization', `Bearer ${accessToken}`)
91+
.reply(200, mockBackupKeychain);
92+
nock(bitgoApiUrl)
93+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
94+
.matchHeader('authorization', `Bearer ${accessToken}`)
95+
.reply(200, mockBitgoKeychain);
96+
7697
const accelerateTransactionStub = sinon
7798
.stub(Wallet.prototype, 'accelerateTransaction')
7899
.resolves({
@@ -117,11 +138,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => {
117138
.matchHeader('authorization', `Bearer ${accessToken}`)
118139
.reply(200, mockWalletData);
119140

141+
// Signing keychain fetched by getWalletAndSigningKeychain
120142
const keychainGetNock = nock(bitgoApiUrl)
121143
.get(`/api/v2/${coin}/key/backup-key-id`)
122144
.matchHeader('authorization', `Bearer ${accessToken}`)
123145
.reply(200, mockBackupKeychain);
124146

147+
// All 3 keychains fetched for walletPubs
148+
nock(bitgoApiUrl)
149+
.get(`/api/v2/${coin}/key/user-key-id`)
150+
.matchHeader('authorization', `Bearer ${accessToken}`)
151+
.reply(200, mockUserKeychain);
152+
nock(bitgoApiUrl)
153+
.get(`/api/v2/${coin}/key/backup-key-id`)
154+
.matchHeader('authorization', `Bearer ${accessToken}`)
155+
.reply(200, mockBackupKeychain);
156+
nock(bitgoApiUrl)
157+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
158+
.matchHeader('authorization', `Bearer ${accessToken}`)
159+
.reply(200, mockBitgoKeychain);
160+
125161
const accelerateTransactionStub = sinon
126162
.stub(Wallet.prototype, 'accelerateTransaction')
127163
.resolves({
@@ -157,11 +193,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => {
157193
.matchHeader('authorization', `Bearer ${accessToken}`)
158194
.reply(200, mockWalletData);
159195

196+
// Signing keychain fetched by getWalletAndSigningKeychain
160197
const keychainGetNock = nock(bitgoApiUrl)
161198
.get(`/api/v2/${coin}/key/user-key-id`)
162199
.matchHeader('authorization', `Bearer ${accessToken}`)
163200
.reply(200, mockUserKeychain);
164201

202+
// All 3 keychains fetched for walletPubs
203+
nock(bitgoApiUrl)
204+
.get(`/api/v2/${coin}/key/user-key-id`)
205+
.matchHeader('authorization', `Bearer ${accessToken}`)
206+
.reply(200, mockUserKeychain);
207+
nock(bitgoApiUrl)
208+
.get(`/api/v2/${coin}/key/backup-key-id`)
209+
.matchHeader('authorization', `Bearer ${accessToken}`)
210+
.reply(200, mockBackupKeychain);
211+
nock(bitgoApiUrl)
212+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
213+
.matchHeader('authorization', `Bearer ${accessToken}`)
214+
.reply(200, mockBitgoKeychain);
215+
165216
const accelerateTransactionStub = sinon
166217
.stub(Wallet.prototype, 'accelerateTransaction')
167218
.resolves({
@@ -324,11 +375,26 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => {
324375
.matchHeader('authorization', `Bearer ${accessToken}`)
325376
.reply(200, mockWalletData);
326377

378+
// Signing keychain fetched by getWalletAndSigningKeychain
327379
const keychainGetNock = nock(bitgoApiUrl)
328380
.get(`/api/v2/${coin}/key/user-key-id`)
329381
.matchHeader('authorization', `Bearer ${accessToken}`)
330382
.reply(200, mockUserKeychain);
331383

384+
// All 3 keychains fetched for walletPubs
385+
nock(bitgoApiUrl)
386+
.get(`/api/v2/${coin}/key/user-key-id`)
387+
.matchHeader('authorization', `Bearer ${accessToken}`)
388+
.reply(200, mockUserKeychain);
389+
nock(bitgoApiUrl)
390+
.get(`/api/v2/${coin}/key/backup-key-id`)
391+
.matchHeader('authorization', `Bearer ${accessToken}`)
392+
.reply(200, mockBackupKeychain);
393+
nock(bitgoApiUrl)
394+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
395+
.matchHeader('authorization', `Bearer ${accessToken}`)
396+
.reply(200, mockBitgoKeychain);
397+
332398
const accelerateTransactionStub = sinon
333399
.stub(Wallet.prototype, 'accelerateTransaction')
334400
.rejects(new Error('Insufficient funds for acceleration'));
@@ -408,4 +474,128 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/accelerate', () => {
408474
response.body.should.have.property('error', 'Internal Server Error');
409475
response.body.should.have.property('details');
410476
});
477+
478+
it('should pass walletPubs (all 3 xpubs) to AWM for UTXO signing', async () => {
479+
nock(bitgoApiUrl)
480+
.get(`/api/v2/${coin}/wallet/${walletId}`)
481+
.matchHeader('authorization', `Bearer ${accessToken}`)
482+
.reply(200, mockWalletData);
483+
484+
// Signing keychain (user) — fetched once by getWalletAndSigningKeychain
485+
nock(bitgoApiUrl)
486+
.get(`/api/v2/${coin}/key/user-key-id`)
487+
.matchHeader('authorization', `Bearer ${accessToken}`)
488+
.reply(200, mockUserKeychain);
489+
490+
// All 3 keychains fetched for walletPubs
491+
nock(bitgoApiUrl)
492+
.get(`/api/v2/${coin}/key/user-key-id`)
493+
.matchHeader('authorization', `Bearer ${accessToken}`)
494+
.reply(200, mockUserKeychain);
495+
496+
nock(bitgoApiUrl)
497+
.get(`/api/v2/${coin}/key/backup-key-id`)
498+
.matchHeader('authorization', `Bearer ${accessToken}`)
499+
.reply(200, mockBackupKeychain);
500+
501+
nock(bitgoApiUrl)
502+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
503+
.matchHeader('authorization', `Bearer ${accessToken}`)
504+
.reply(200, mockBitgoKeychain);
505+
506+
let capturedSignBody: any;
507+
const awmSignNock = nock(advancedWalletManagerUrl)
508+
.post(`/api/${coin}/multisig/sign`, (body) => {
509+
capturedSignBody = body;
510+
return true;
511+
})
512+
.reply(200, {
513+
halfSigned: { txHex: 'signed-tx-hex' },
514+
source: 'user',
515+
pub: mockUserKeychain.pub,
516+
});
517+
518+
// Stub accelerateTransaction to call customSigningFunction so the AWM request is made
519+
sinon.stub(Wallet.prototype, 'accelerateTransaction').callsFake(async (params: any) => {
520+
await params.customSigningFunction({ txPrebuild: { txHex: 'prebuilt-tx' } });
521+
return { txid: 'accelerated-tx-id', tx: '0100000001abcdef...', status: 'signed' };
522+
});
523+
524+
const response = await agent
525+
.post(`/api/v1/${coin}/advancedwallet/${walletId}/accelerate`)
526+
.set('Authorization', `Bearer ${accessToken}`)
527+
.send({
528+
pubkey: mockUserKeychain.pub,
529+
source: 'user',
530+
cpfpTxIds: ['b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26'],
531+
cpfpFeeRate: 50,
532+
});
533+
534+
response.status.should.equal(200);
535+
awmSignNock.done();
536+
capturedSignBody.should.have.property('walletPubs');
537+
capturedSignBody.walletPubs.should.deepEqual([
538+
mockUserKeychain.pub,
539+
mockBackupKeychain.pub,
540+
mockBitgoKeychain.pub,
541+
]);
542+
});
543+
544+
it('should omit walletPubs from AWM request when any keychain is missing a pub', async () => {
545+
nock(bitgoApiUrl)
546+
.get(`/api/v2/${coin}/wallet/${walletId}`)
547+
.matchHeader('authorization', `Bearer ${accessToken}`)
548+
.reply(200, mockWalletData);
549+
550+
nock(bitgoApiUrl)
551+
.get(`/api/v2/${coin}/key/user-key-id`)
552+
.matchHeader('authorization', `Bearer ${accessToken}`)
553+
.reply(200, mockUserKeychain);
554+
555+
nock(bitgoApiUrl)
556+
.get(`/api/v2/${coin}/key/user-key-id`)
557+
.matchHeader('authorization', `Bearer ${accessToken}`)
558+
.reply(200, mockUserKeychain);
559+
560+
nock(bitgoApiUrl)
561+
.get(`/api/v2/${coin}/key/backup-key-id`)
562+
.matchHeader('authorization', `Bearer ${accessToken}`)
563+
.reply(200, { id: 'backup-key-id' }); // no pub
564+
565+
nock(bitgoApiUrl)
566+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
567+
.matchHeader('authorization', `Bearer ${accessToken}`)
568+
.reply(200, mockBitgoKeychain);
569+
570+
let capturedSignBody: any;
571+
const awmSignNock = nock(advancedWalletManagerUrl)
572+
.post(`/api/${coin}/multisig/sign`, (body) => {
573+
capturedSignBody = body;
574+
return true;
575+
})
576+
.reply(200, {
577+
halfSigned: { txHex: 'signed-tx-hex' },
578+
source: 'user',
579+
pub: mockUserKeychain.pub,
580+
});
581+
582+
sinon.stub(Wallet.prototype, 'accelerateTransaction').callsFake(async (params: any) => {
583+
await params.customSigningFunction({ txPrebuild: { txHex: 'prebuilt-tx' } });
584+
return { txid: 'accelerated-tx-id', tx: '0100000001abcdef...', status: 'signed' };
585+
});
586+
587+
const response = await agent
588+
.post(`/api/v1/${coin}/advancedwallet/${walletId}/accelerate`)
589+
.set('Authorization', `Bearer ${accessToken}`)
590+
.send({
591+
pubkey: mockUserKeychain.pub,
592+
source: 'user',
593+
cpfpTxIds: ['b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26'],
594+
cpfpFeeRate: 50,
595+
});
596+
597+
response.status.should.equal(200);
598+
awmSignNock.done();
599+
capturedSignBody.should.not.have.property('walletPubs');
600+
});
411601
});

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
3838
type: 'independent',
3939
};
4040

41+
const mockBitgoKeychain = {
42+
id: 'bitgo-key-id',
43+
pub: 'xpub661MyMwAqRbcHtYNxRNuEtDFmPMRzBVPDfBXNu2RUBVFNz8MnWQgkrMZCNB',
44+
type: 'bitgo',
45+
};
46+
4147
before(() => {
4248
nock.disableNetConnect();
4349
nock.enableNetConnect('127.0.0.1');
@@ -77,6 +83,20 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
7783
.matchHeader('authorization', `Bearer ${accessToken}`)
7884
.reply(200, mockUserKeychain);
7985

86+
// All 3 keychains fetched for walletPubs
87+
nock(bitgoApiUrl)
88+
.get(`/api/v2/${coin}/key/user-key-id`)
89+
.matchHeader('authorization', `Bearer ${accessToken}`)
90+
.reply(200, mockUserKeychain);
91+
nock(bitgoApiUrl)
92+
.get(`/api/v2/${coin}/key/backup-key-id`)
93+
.matchHeader('authorization', `Bearer ${accessToken}`)
94+
.reply(200, mockBackupKeychain);
95+
nock(bitgoApiUrl)
96+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
97+
.matchHeader('authorization', `Bearer ${accessToken}`)
98+
.reply(200, mockBitgoKeychain);
99+
80100
const mockBuilds = [
81101
{
82102
walletId,
@@ -147,6 +167,20 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
147167
.matchHeader('authorization', `Bearer ${accessToken}`)
148168
.reply(200, { ...mockUserKeychain, commonKeychain: 'user-common-key' });
149169

170+
// All 3 keychains fetched for walletPubs
171+
nock(bitgoApiUrl)
172+
.get(`/api/v2/${coin}/key/user-key-id`)
173+
.matchHeader('authorization', `Bearer ${accessToken}`)
174+
.reply(200, { ...mockUserKeychain, commonKeychain: 'user-common-key' });
175+
nock(bitgoApiUrl)
176+
.get(`/api/v2/${coin}/key/backup-key-id`)
177+
.matchHeader('authorization', `Bearer ${accessToken}`)
178+
.reply(200, mockBackupKeychain);
179+
nock(bitgoApiUrl)
180+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
181+
.matchHeader('authorization', `Bearer ${accessToken}`)
182+
.reply(200, mockBitgoKeychain);
183+
150184
const mockMpcBuild = {
151185
walletId,
152186
txHex: 'unsigned-mpc-tx-hex-1',
@@ -234,6 +268,20 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
234268
.matchHeader('authorization', `Bearer ${accessToken}`)
235269
.reply(200, mockBackupKeychain);
236270

271+
// All 3 keychains fetched for walletPubs
272+
nock(bitgoApiUrl)
273+
.get(`/api/v2/${coin}/key/user-key-id`)
274+
.matchHeader('authorization', `Bearer ${accessToken}`)
275+
.reply(200, mockUserKeychain);
276+
nock(bitgoApiUrl)
277+
.get(`/api/v2/${coin}/key/backup-key-id`)
278+
.matchHeader('authorization', `Bearer ${accessToken}`)
279+
.reply(200, mockBackupKeychain);
280+
nock(bitgoApiUrl)
281+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
282+
.matchHeader('authorization', `Bearer ${accessToken}`)
283+
.reply(200, mockBitgoKeychain);
284+
237285
const mockBuild = {
238286
walletId,
239287
txHex: 'unsigned-tx-hex-backup',
@@ -365,6 +413,20 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
365413
.matchHeader('authorization', `Bearer ${accessToken}`)
366414
.reply(200, mockUserKeychain);
367415

416+
// All 3 keychains fetched for walletPubs
417+
nock(bitgoApiUrl)
418+
.get(`/api/v2/${coin}/key/user-key-id`)
419+
.matchHeader('authorization', `Bearer ${accessToken}`)
420+
.reply(200, mockUserKeychain);
421+
nock(bitgoApiUrl)
422+
.get(`/api/v2/${coin}/key/backup-key-id`)
423+
.matchHeader('authorization', `Bearer ${accessToken}`)
424+
.reply(200, mockBackupKeychain);
425+
nock(bitgoApiUrl)
426+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
427+
.matchHeader('authorization', `Bearer ${accessToken}`)
428+
.reply(200, mockBitgoKeychain);
429+
368430
const allowsConsolidationsStub = sinon
369431
.stub(Hteth.prototype, 'allowsAccountConsolidations')
370432
.returns(false);
@@ -458,6 +520,20 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
458520
.matchHeader('authorization', `Bearer ${accessToken}`)
459521
.reply(200, mockUserKeychain);
460522

523+
// All 3 keychains fetched for walletPubs
524+
nock(bitgoApiUrl)
525+
.get(`/api/v2/${coin}/key/user-key-id`)
526+
.matchHeader('authorization', `Bearer ${accessToken}`)
527+
.reply(200, mockUserKeychain);
528+
nock(bitgoApiUrl)
529+
.get(`/api/v2/${coin}/key/backup-key-id`)
530+
.matchHeader('authorization', `Bearer ${accessToken}`)
531+
.reply(200, mockBackupKeychain);
532+
nock(bitgoApiUrl)
533+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
534+
.matchHeader('authorization', `Bearer ${accessToken}`)
535+
.reply(200, mockBitgoKeychain);
536+
461537
const mockBuilds = [
462538
{ walletId, txHex: 'unsigned-tx-hex-1' },
463539
{ walletId, txHex: 'unsigned-tx-hex-2' },
@@ -516,6 +592,20 @@ describe('POST /api/v1/:coin/advancedwallet/:walletId/consolidate', () => {
516592
.matchHeader('authorization', `Bearer ${accessToken}`)
517593
.reply(200, mockUserKeychain);
518594

595+
// All 3 keychains fetched for walletPubs
596+
nock(bitgoApiUrl)
597+
.get(`/api/v2/${coin}/key/user-key-id`)
598+
.matchHeader('authorization', `Bearer ${accessToken}`)
599+
.reply(200, mockUserKeychain);
600+
nock(bitgoApiUrl)
601+
.get(`/api/v2/${coin}/key/backup-key-id`)
602+
.matchHeader('authorization', `Bearer ${accessToken}`)
603+
.reply(200, mockBackupKeychain);
604+
nock(bitgoApiUrl)
605+
.get(`/api/v2/${coin}/key/bitgo-key-id`)
606+
.matchHeader('authorization', `Bearer ${accessToken}`)
607+
.reply(200, mockBitgoKeychain);
608+
519609
const mockBuilds = [
520610
{ walletId, txHex: 'unsigned-tx-hex-1' },
521611
{ walletId, txHex: 'unsigned-tx-hex-2' },

0 commit comments

Comments
 (0)