Skip to content

Commit 04171a2

Browse files
committed
test: add integration test for tx acceleration
Ticket: WCN-768
1 parent 64a241c commit 04171a2

3 files changed

Lines changed: 159 additions & 4 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import 'should';
2+
import { startServices, IntegServices } from './helpers/setup';
3+
import { LOCALHOST } from './helpers/servers';
4+
import { SigningMode } from '../../shared/types';
5+
6+
/**
7+
* Deterministic test keypair derived from Buffer.alloc(64, 0x42) — a public, reproducible seed.
8+
* Not a secret. Never funded. Matches getKeychain.user.json and prebuildTx.tbtc.json.
9+
*/
10+
const USER_XPUB =
11+
'xpub661MyMwAqRbcEvJQx6spkkHLRgtjxmVdyDSvbDt2m9NFpbkHdcu5WJsHHHqFxNATbNHnhMWJiwckoMqF75EpcNhU9xeVM4oDS7urM3os4BH';
12+
const USER_XPRV =
13+
'xprv9s21ZrQH143K2SDwr5LpPcLbsf4FZJmnbzXKnqURCoqGwoR965apxWYoS2DKu2ivcMTB9uTK6XhZDEPfTeNXGf7mmACuMN6cFS5ttmrpZ3i';
14+
15+
const WALLET_ID = 'test-wallet-id';
16+
const CPFP_TX_ID = 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26';
17+
18+
const accelerateRequestBody = {
19+
pubkey: USER_XPUB,
20+
source: 'user' as const,
21+
cpfpTxIds: [CPFP_TX_ID],
22+
cpfpFeeRate: 50,
23+
maxFee: 10000,
24+
};
25+
26+
describe('Accelerate: EXTERNAL signing', () => {
27+
let services: IntegServices;
28+
29+
before(async () => {
30+
services = await startServices({ signingMode: SigningMode.EXTERNAL });
31+
});
32+
33+
after(async () => {
34+
await services.teardown();
35+
});
36+
37+
beforeEach(() => {
38+
services.keyProvider.calls.length = 0;
39+
services.bitgo.calls.length = 0;
40+
});
41+
42+
it('accelerates a tbtc transaction via CPFP using external key provider', async () => {
43+
const res = await fetch(
44+
`http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/${WALLET_ID}/accelerate`,
45+
{
46+
method: 'POST',
47+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' },
48+
body: JSON.stringify(accelerateRequestBody),
49+
},
50+
);
51+
52+
res.status.should.equal(200);
53+
const body = (await res.json()) as { txid: string; tx: string; status: string };
54+
body.should.have.property('txid', 'test-tx-id');
55+
body.should.have.property('tx', '01000000000101030a0000');
56+
body.should.have.property('status', 'signed');
57+
58+
/**
59+
* In external mode, AWM delegates signing to the key provider.
60+
* POST /sign must be called — not POST /key (no local key retrieval for signing).
61+
*/
62+
services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(1);
63+
services.keyProvider.calls.filter((c) => c.path === '/key').should.have.length(0);
64+
65+
/** BitGo must receive tx/build, block/latest, and tx/send */
66+
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(1);
67+
services.bitgo.calls
68+
.filter((c) => c.path.endsWith('/public/block/latest'))
69+
.should.have.length(1);
70+
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send')).should.have.length(1);
71+
});
72+
});
73+
74+
describe('Accelerate: LOCAL signing', () => {
75+
let services: IntegServices;
76+
77+
before(async () => {
78+
services = await startServices({ signingMode: SigningMode.LOCAL });
79+
80+
/**
81+
* Seed the mock key provider with a known xprv so AWM can retrieve it
82+
* via GET /key/:pub and sign the PSBT locally. The xpub must match
83+
* getKeychain.user.json and prebuildTx.accelerate.tbtc.json.
84+
*/
85+
await fetch(`http://127.0.0.1:${services.keyProvider.port}/key`, {
86+
method: 'POST',
87+
headers: { 'Content-Type': 'application/json' },
88+
body: JSON.stringify({
89+
pub: USER_XPUB,
90+
prv: USER_XPRV,
91+
coin: 'tbtc',
92+
source: 'user',
93+
type: 'independent',
94+
}),
95+
});
96+
});
97+
98+
after(async () => {
99+
await services.teardown();
100+
});
101+
102+
beforeEach(() => {
103+
services.keyProvider.calls.length = 0;
104+
services.bitgo.calls.length = 0;
105+
});
106+
107+
it('accelerates a tbtc transaction via CPFP using locally stored xprv', async () => {
108+
const res = await fetch(
109+
`http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/${WALLET_ID}/accelerate`,
110+
{
111+
method: 'POST',
112+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' },
113+
body: JSON.stringify(accelerateRequestBody),
114+
},
115+
);
116+
117+
res.status.should.equal(200);
118+
const body = (await res.json()) as { txid: string; tx: string; status: string };
119+
body.should.have.property('txid', 'test-tx-id');
120+
body.should.have.property('tx', '01000000000101030a0000');
121+
body.should.have.property('status', 'signed');
122+
123+
/**
124+
* In local mode, AWM retrieves the xprv via GET /key/:pub and signs internally.
125+
* POST /sign must NOT be called — signing happens inside AWM, not in the key provider.
126+
*/
127+
services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(0);
128+
services.keyProvider.calls.filter((c) => c.path.startsWith('/key/')).length.should.be.above(0);
129+
130+
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(1);
131+
services.bitgo.calls
132+
.filter((c) => c.path.endsWith('/public/block/latest'))
133+
.should.have.length(1);
134+
services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send')).should.have.length(1);
135+
});
136+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"txHex": "70736274ff01000a01000000000000000000000000",
3+
"txInfo": { "nP2SHInputs": 0, "nSegwitInputs": 0, "nOutputs": 0 }
4+
}

src/__tests__/integration/helpers/mockBitgoServer.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,22 @@ type SendManyFixtureMethod = 'getWallet' | 'prebuildTx' | 'sendTx';
2525
type SupportedCoin = 'hteth' | 'tbtc';
2626
type CoinToFixtures<C extends SupportedCoin> = {
2727
[K in SendManyFixtureMethod]: `${K}.${C}`;
28-
};
28+
} & { acceleratePrebuildTx: string };
2929

3030
/** Registry — add a new coin here to support it across all sendMany integ test routes */
3131
const COIN_FIXTURES: { [C in SupportedCoin]: CoinToFixtures<C> } = {
32-
hteth: { getWallet: 'getWallet.hteth', prebuildTx: 'prebuildTx.hteth', sendTx: 'sendTx.hteth' },
33-
tbtc: { getWallet: 'getWallet.tbtc', prebuildTx: 'prebuildTx.tbtc', sendTx: 'sendTx.tbtc' },
32+
hteth: {
33+
getWallet: 'getWallet.hteth',
34+
prebuildTx: 'prebuildTx.hteth',
35+
sendTx: 'sendTx.hteth',
36+
acceleratePrebuildTx: 'prebuildTx.hteth', // CPFP/RBF not applicable to EVM; reuses standard prebuild
37+
},
38+
tbtc: {
39+
getWallet: 'getWallet.tbtc',
40+
prebuildTx: 'prebuildTx.tbtc',
41+
sendTx: 'sendTx.tbtc',
42+
acceleratePrebuildTx: 'prebuildTx.accelerate.tbtc',
43+
},
3444
};
3545

3646
function coinFixtures(coin: string): CoinToFixtures<SupportedCoin> {
@@ -99,7 +109,12 @@ export async function startMockBitgoServer(): Promise<MockBitgoServer> {
99109

100110
/** Transaction prebuild — coin-specific fixture */
101111
app.post('/api/v2/:coin/wallet/:walletId/tx/build', (req, res) => {
102-
res.json(loadFixture(coinFixtures(req.params.coin).prebuildTx));
112+
const { coin } = req.params;
113+
const isAccelerate = req.body?.cpfpTxIds?.length || req.body?.rbfTxIds?.length;
114+
const fixtureName = isAccelerate
115+
? coinFixtures(coin).acceleratePrebuildTx
116+
: coinFixtures(coin).prebuildTx;
117+
res.json(loadFixture(fixtureName));
103118
});
104119

105120
/** Transaction submit — coin-specific fixture */

0 commit comments

Comments
 (0)