1+ import 'should' ;
2+ import sinon from 'sinon' ;
3+ import * as request from 'supertest' ;
4+ import nock from 'nock' ;
5+ import { app as expressApp } from '../../../masterExpressApp' ;
6+ import { AppMode , MasterExpressConfig , TlsMode } from '../../../shared/types' ;
7+ import { Environments , Wallet } from '@bitgo/sdk-core' ;
8+ import { Tbtc } from '@bitgo/sdk-coin-btc' ;
9+
10+ describe ( 'POST /api/:coin/wallet/:walletId/accelerate' , ( ) => {
11+ let agent : request . SuperAgentTest ;
12+ const coin = 'tbtc' ;
13+ const walletId = 'test-wallet-id' ;
14+ const accessToken = 'test-access-token' ;
15+ const bitgoApiUrl = Environments . test . uri ;
16+ const enclavedExpressUrl = 'https://test-enclaved-express.com' ;
17+
18+ before ( ( ) => {
19+ nock . disableNetConnect ( ) ;
20+ nock . enableNetConnect ( '127.0.0.1' ) ;
21+
22+ const config : MasterExpressConfig = {
23+ appMode : AppMode . MASTER_EXPRESS ,
24+ port : 0 , // Let OS assign a free port
25+ bind : 'localhost' ,
26+ timeout : 30000 ,
27+ logFile : '' ,
28+ env : 'test' ,
29+ disableEnvCheck : true ,
30+ authVersion : 2 ,
31+ enclavedExpressUrl : enclavedExpressUrl ,
32+ enclavedExpressCert : 'test-cert' ,
33+ tlsMode : TlsMode . DISABLED ,
34+ mtlsRequestCert : false ,
35+ allowSelfSigned : true ,
36+ } ;
37+
38+ const app = expressApp ( config ) ;
39+ agent = request . agent ( app ) ;
40+ } ) ;
41+
42+ afterEach ( ( ) => {
43+ nock . cleanAll ( ) ;
44+ sinon . restore ( ) ;
45+ } ) ;
46+
47+ it ( 'should accelerate transaction by calling the enclaved express service' , async ( ) => {
48+ // Mock wallet get request
49+ const walletGetNock = nock ( bitgoApiUrl )
50+ . get ( `/api/v2/${ coin } /wallet/${ walletId } ` )
51+ . matchHeader ( 'any' , ( ) => true )
52+ . reply ( 200 , {
53+ id : walletId ,
54+ type : 'cold' ,
55+ subType : 'onPrem' ,
56+ keys : [ 'user-key-id' , 'backup-key-id' , 'bitgo-key-id' ] ,
57+ } ) ;
58+
59+ // Mock keychain get request
60+ const keychainGetNock = nock ( bitgoApiUrl )
61+ . get ( `/api/v2/${ coin } /key/user-key-id` )
62+ . matchHeader ( 'any' , ( ) => true )
63+ . reply ( 200 , {
64+ id : 'user-key-id' ,
65+ pub : 'xpub_user' ,
66+ } ) ;
67+
68+ // Mock accelerateTransaction
69+ const accelerateTransactionStub = sinon
70+ . stub ( Wallet . prototype , 'accelerateTransaction' )
71+ . resolves ( {
72+ txid : 'accelerated-tx-id' ,
73+ tx : "accerated-transaction-hex" ,
74+ status : 'signed' ,
75+ } ) ;
76+
77+ const response = await agent
78+ . post ( `/api/${ coin } /wallet/${ walletId } /accelerate` )
79+ . set ( 'Authorization' , `Bearer ${ accessToken } ` )
80+ . send ( {
81+ source : 'user' ,
82+ pubkey : 'xpub_user' ,
83+ cpfpTxIds : [ 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26' ] ,
84+ cpfpFeeRate : 50 ,
85+ maxFee : 10000 ,
86+ } ) ;
87+
88+ response . status . should . equal ( 200 ) ;
89+ response . body . should . have . property ( 'txid' , 'accelerated-tx-id' ) ;
90+ response . body . should . have . property ( 'status' , 'signed' ) ;
91+
92+ walletGetNock . done ( ) ;
93+ keychainGetNock . done ( ) ;
94+ sinon . assert . calledOnce ( accelerateTransactionStub ) ;
95+ } ) ;
96+
97+ it ( 'should handle acceleration with backup key signing' , async ( ) => {
98+ // Mock wallet get request
99+ const walletGetNock = nock ( bitgoApiUrl )
100+ . get ( `/api/v2/${ coin } /wallet/${ walletId } ` )
101+ . matchHeader ( 'any' , ( ) => true )
102+ . reply ( 200 , {
103+ id : walletId ,
104+ type : 'cold' ,
105+ subType : 'onPrem' ,
106+ keys : [ 'user-key-id' , 'backup-key-id' , 'bitgo-key-id' ] ,
107+ } ) ;
108+
109+ // Mock keychain get request for backup key
110+ const keychainGetNock = nock ( bitgoApiUrl )
111+ . get ( `/api/v2/${ coin } /key/backup-key-id` )
112+ . matchHeader ( 'any' , ( ) => true )
113+ . reply ( 200 , {
114+ id : 'backup-key-id' ,
115+ pub : 'xpub_backup' ,
116+ } ) ;
117+
118+ // Mock accelerateTransaction
119+ const accelerateTransactionStub = sinon
120+ . stub ( Wallet . prototype , 'accelerateTransaction' )
121+ . resolves ( {
122+ txid : 'accelerated-tx-id' ,
123+ status : 'signed' ,
124+ tx : "accelerated-transaction-hex" ,
125+ } ) ;
126+
127+ const response = await agent
128+ . post ( `/api/${ coin } /wallet/${ walletId } /accelerate` )
129+ . set ( 'Authorization' , `Bearer ${ accessToken } ` )
130+ . send ( {
131+ source : 'backup' ,
132+ pubkey : 'xpub_backup' ,
133+ rbfTxIds : [ 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26' ] ,
134+ feeMultiplier : 1.5 ,
135+ } ) ;
136+
137+ response . status . should . equal ( 200 ) ;
138+ response . body . should . have . property ( 'txid' , 'accelerated-tx-id' ) ;
139+
140+ walletGetNock . done ( ) ;
141+ keychainGetNock . done ( ) ;
142+ sinon . assert . calledOnce ( accelerateTransactionStub ) ;
143+ } ) ;
144+
145+ it ( 'should throw error when wallet not found' , async ( ) => {
146+ // Mock wallet get request to return 404
147+ const walletGetNock = nock ( bitgoApiUrl )
148+ . get ( `/api/v2/${ coin } /wallet/${ walletId } ` )
149+ . matchHeader ( 'any' , ( ) => true )
150+ . reply ( 404 , { error : 'Wallet not found' } ) ;
151+
152+ const response = await agent
153+ . post ( `/api/${ coin } /wallet/${ walletId } /accelerate` )
154+ . set ( 'Authorization' , `Bearer ${ accessToken } ` )
155+ . send ( {
156+ source : 'user' ,
157+ pubkey : 'xpub_user' ,
158+ cpfpTxIds : [ 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26' ] ,
159+ } ) ;
160+
161+ response . status . should . equal ( 500 ) ;
162+ response . body . should . have . property ( 'error' ) ;
163+
164+ walletGetNock . done ( ) ;
165+ } ) ;
166+
167+ it ( 'should throw error when signing keychain not found' , async ( ) => {
168+ // Mock wallet get request
169+ const walletGetNock = nock ( bitgoApiUrl )
170+ . get ( `/api/v2/${ coin } /wallet/${ walletId } ` )
171+ . matchHeader ( 'any' , ( ) => true )
172+ . reply ( 200 , {
173+ id : walletId ,
174+ type : 'cold' ,
175+ subType : 'onPrem' ,
176+ keys : [ 'user-key-id' , 'backup-key-id' , 'bitgo-key-id' ] ,
177+ } ) ;
178+
179+ // Mock keychain get request to return 404
180+ const keychainGetNock = nock ( bitgoApiUrl )
181+ . get ( `/api/v2/${ coin } /key/user-key-id` )
182+ . matchHeader ( 'any' , ( ) => true )
183+ . reply ( 404 , { error : 'Keychain not found' } ) ;
184+
185+ const response = await agent
186+ . post ( `/api/${ coin } /wallet/${ walletId } /accelerate` )
187+ . set ( 'Authorization' , `Bearer ${ accessToken } ` )
188+ . send ( {
189+ source : 'user' ,
190+ pubkey : 'xpub_user' ,
191+ cpfpTxIds : [ 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26' ] ,
192+ } ) ;
193+
194+ response . status . should . equal ( 500 ) ;
195+ response . body . should . have . property ( 'error' ) ;
196+
197+ walletGetNock . done ( ) ;
198+ keychainGetNock . done ( ) ;
199+ } ) ;
200+
201+ it ( 'should throw error when provided pubkey does not match wallet keychain' , async ( ) => {
202+ // Mock wallet get request
203+ const walletGetNock = nock ( bitgoApiUrl )
204+ . get ( `/api/v2/${ coin } /wallet/${ walletId } ` )
205+ . matchHeader ( 'any' , ( ) => true )
206+ . reply ( 200 , {
207+ id : walletId ,
208+ type : 'cold' ,
209+ subType : 'onPrem' ,
210+ keys : [ 'user-key-id' , 'backup-key-id' , 'bitgo-key-id' ] ,
211+ } ) ;
212+
213+ // Mock keychain get request
214+ const keychainGetNock = nock ( bitgoApiUrl )
215+ . get ( `/api/v2/${ coin } /key/user-key-id` )
216+ . matchHeader ( 'any' , ( ) => true )
217+ . reply ( 200 , {
218+ id : 'user-key-id' ,
219+ pub : 'xpub_user' ,
220+ } ) ;
221+
222+ const response = await agent
223+ . post ( `/api/${ coin } /wallet/${ walletId } /accelerate` )
224+ . set ( 'Authorization' , `Bearer ${ accessToken } ` )
225+ . send ( {
226+ source : 'user' ,
227+ pubkey : 'wrong_pubkey' ,
228+ cpfpTxIds : [ 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26' ] ,
229+ } ) ;
230+
231+ response . status . should . equal ( 500 ) ;
232+ response . body . should . have . property ( 'error' ) ;
233+
234+ walletGetNock . done ( ) ;
235+ keychainGetNock . done ( ) ;
236+ } ) ;
237+
238+ it ( 'should handle acceleration with additional parameters' , async ( ) => {
239+ // Mock wallet get request
240+ const walletGetNock = nock ( bitgoApiUrl )
241+ . get ( `/api/v2/${ coin } /wallet/${ walletId } ` )
242+ . matchHeader ( 'any' , ( ) => true )
243+ . reply ( 200 , {
244+ id : walletId ,
245+ type : 'cold' ,
246+ subType : 'onPrem' ,
247+ keys : [ 'user-key-id' , 'backup-key-id' , 'bitgo-key-id' ] ,
248+ } ) ;
249+
250+ // Mock keychain get request
251+ const keychainGetNock = nock ( bitgoApiUrl )
252+ . get ( `/api/v2/${ coin } /key/user-key-id` )
253+ . matchHeader ( 'any' , ( ) => true )
254+ . reply ( 200 , {
255+ id : 'user-key-id' ,
256+ pub : 'xpub_user' ,
257+ } ) ;
258+
259+ // Mock accelerateTransaction
260+ const accelerateTransactionStub = sinon
261+ . stub ( Wallet . prototype , 'accelerateTransaction' )
262+ . resolves ( {
263+ txid : 'accelerated-tx-id' ,
264+ status : 'signed' ,
265+ tx : "accelerated-transaction-hex" ,
266+ } ) ;
267+
268+ const response = await agent
269+ . post ( `/api/${ coin } /wallet/${ walletId } /accelerate` )
270+ . set ( 'Authorization' , `Bearer ${ accessToken } ` )
271+ . send ( {
272+ source : 'user' ,
273+ pubkey : 'xpub_user' ,
274+ cpfpTxIds : [ 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26' ] ,
275+ cpfpFeeRate : 100 ,
276+ maxFee : 20000 ,
277+ feeMultiplier : 2.0 ,
278+ } ) ;
279+
280+ response . status . should . equal ( 200 ) ;
281+ response . body . should . have . property ( 'txid' , 'accelerated-tx-id' ) ;
282+
283+ walletGetNock . done ( ) ;
284+ keychainGetNock . done ( ) ;
285+ sinon . assert . calledOnce ( accelerateTransactionStub ) ;
286+ } ) ;
287+ } ) ;
0 commit comments