From 04171a238453aa0194e9ff3867012e0d784898d4 Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Fri, 5 Jun 2026 00:19:12 -0400 Subject: [PATCH 1/2] test: add integration test for tx acceleration Ticket: WCN-768 --- .../integration/accelerate.integ.test.ts | 136 ++++++++++++++++++ .../bitgo/prebuildTx.accelerate.tbtc.json | 4 + .../integration/helpers/mockBitgoServer.ts | 23 ++- 3 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/integration/accelerate.integ.test.ts create mode 100644 src/__tests__/integration/fixtures/bitgo/prebuildTx.accelerate.tbtc.json diff --git a/src/__tests__/integration/accelerate.integ.test.ts b/src/__tests__/integration/accelerate.integ.test.ts new file mode 100644 index 0000000..d842dd4 --- /dev/null +++ b/src/__tests__/integration/accelerate.integ.test.ts @@ -0,0 +1,136 @@ +import 'should'; +import { startServices, IntegServices } from './helpers/setup'; +import { LOCALHOST } from './helpers/servers'; +import { SigningMode } from '../../shared/types'; + +/** + * Deterministic test keypair derived from Buffer.alloc(64, 0x42) — a public, reproducible seed. + * Not a secret. Never funded. Matches getKeychain.user.json and prebuildTx.tbtc.json. + */ +const USER_XPUB = + 'xpub661MyMwAqRbcEvJQx6spkkHLRgtjxmVdyDSvbDt2m9NFpbkHdcu5WJsHHHqFxNATbNHnhMWJiwckoMqF75EpcNhU9xeVM4oDS7urM3os4BH'; +const USER_XPRV = + 'xprv9s21ZrQH143K2SDwr5LpPcLbsf4FZJmnbzXKnqURCoqGwoR965apxWYoS2DKu2ivcMTB9uTK6XhZDEPfTeNXGf7mmACuMN6cFS5ttmrpZ3i'; + +const WALLET_ID = 'test-wallet-id'; +const CPFP_TX_ID = 'b8a828b98dbf32d9fd1875cbace9640ceb8c82626716b4a64203fdc79bb46d26'; + +const accelerateRequestBody = { + pubkey: USER_XPUB, + source: 'user' as const, + cpfpTxIds: [CPFP_TX_ID], + cpfpFeeRate: 50, + maxFee: 10000, +}; + +describe('Accelerate: EXTERNAL signing', () => { + let services: IntegServices; + + before(async () => { + services = await startServices({ signingMode: SigningMode.EXTERNAL }); + }); + + after(async () => { + await services.teardown(); + }); + + beforeEach(() => { + services.keyProvider.calls.length = 0; + services.bitgo.calls.length = 0; + }); + + it('accelerates a tbtc transaction via CPFP using external key provider', async () => { + const res = await fetch( + `http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/${WALLET_ID}/accelerate`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' }, + body: JSON.stringify(accelerateRequestBody), + }, + ); + + res.status.should.equal(200); + const body = (await res.json()) as { txid: string; tx: string; status: string }; + body.should.have.property('txid', 'test-tx-id'); + body.should.have.property('tx', '01000000000101030a0000'); + body.should.have.property('status', 'signed'); + + /** + * In external mode, AWM delegates signing to the key provider. + * POST /sign must be called — not POST /key (no local key retrieval for signing). + */ + services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(1); + services.keyProvider.calls.filter((c) => c.path === '/key').should.have.length(0); + + /** BitGo must receive tx/build, block/latest, and tx/send */ + services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(1); + services.bitgo.calls + .filter((c) => c.path.endsWith('/public/block/latest')) + .should.have.length(1); + services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send')).should.have.length(1); + }); +}); + +describe('Accelerate: LOCAL signing', () => { + let services: IntegServices; + + before(async () => { + services = await startServices({ signingMode: SigningMode.LOCAL }); + + /** + * Seed the mock key provider with a known xprv so AWM can retrieve it + * via GET /key/:pub and sign the PSBT locally. The xpub must match + * getKeychain.user.json and prebuildTx.accelerate.tbtc.json. + */ + await fetch(`http://127.0.0.1:${services.keyProvider.port}/key`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + pub: USER_XPUB, + prv: USER_XPRV, + coin: 'tbtc', + source: 'user', + type: 'independent', + }), + }); + }); + + after(async () => { + await services.teardown(); + }); + + beforeEach(() => { + services.keyProvider.calls.length = 0; + services.bitgo.calls.length = 0; + }); + + it('accelerates a tbtc transaction via CPFP using locally stored xprv', async () => { + const res = await fetch( + `http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/${WALLET_ID}/accelerate`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' }, + body: JSON.stringify(accelerateRequestBody), + }, + ); + + res.status.should.equal(200); + const body = (await res.json()) as { txid: string; tx: string; status: string }; + body.should.have.property('txid', 'test-tx-id'); + body.should.have.property('tx', '01000000000101030a0000'); + body.should.have.property('status', 'signed'); + + /** + * In local mode, AWM retrieves the xprv via GET /key/:pub and signs internally. + * POST /sign must NOT be called — signing happens inside AWM, not in the key provider. + */ + services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(0); + services.keyProvider.calls.filter((c) => c.path.startsWith('/key/')).length.should.be.above(0); + + services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(1); + services.bitgo.calls + .filter((c) => c.path.endsWith('/public/block/latest')) + .should.have.length(1); + services.bitgo.calls.filter((c) => c.path.endsWith('/tx/send')).should.have.length(1); + }); +}); diff --git a/src/__tests__/integration/fixtures/bitgo/prebuildTx.accelerate.tbtc.json b/src/__tests__/integration/fixtures/bitgo/prebuildTx.accelerate.tbtc.json new file mode 100644 index 0000000..93a7c4d --- /dev/null +++ b/src/__tests__/integration/fixtures/bitgo/prebuildTx.accelerate.tbtc.json @@ -0,0 +1,4 @@ +{ + "txHex": "70736274ff01000a01000000000000000000000000", + "txInfo": { "nP2SHInputs": 0, "nSegwitInputs": 0, "nOutputs": 0 } +} diff --git a/src/__tests__/integration/helpers/mockBitgoServer.ts b/src/__tests__/integration/helpers/mockBitgoServer.ts index 10dee8e..dcafd90 100644 --- a/src/__tests__/integration/helpers/mockBitgoServer.ts +++ b/src/__tests__/integration/helpers/mockBitgoServer.ts @@ -25,12 +25,22 @@ type SendManyFixtureMethod = 'getWallet' | 'prebuildTx' | 'sendTx'; type SupportedCoin = 'hteth' | 'tbtc'; type CoinToFixtures = { [K in SendManyFixtureMethod]: `${K}.${C}`; -}; +} & { acceleratePrebuildTx: string }; /** Registry — add a new coin here to support it across all sendMany integ test routes */ const COIN_FIXTURES: { [C in SupportedCoin]: CoinToFixtures } = { - hteth: { getWallet: 'getWallet.hteth', prebuildTx: 'prebuildTx.hteth', sendTx: 'sendTx.hteth' }, - tbtc: { getWallet: 'getWallet.tbtc', prebuildTx: 'prebuildTx.tbtc', sendTx: 'sendTx.tbtc' }, + hteth: { + getWallet: 'getWallet.hteth', + prebuildTx: 'prebuildTx.hteth', + sendTx: 'sendTx.hteth', + acceleratePrebuildTx: 'prebuildTx.hteth', // CPFP/RBF not applicable to EVM; reuses standard prebuild + }, + tbtc: { + getWallet: 'getWallet.tbtc', + prebuildTx: 'prebuildTx.tbtc', + sendTx: 'sendTx.tbtc', + acceleratePrebuildTx: 'prebuildTx.accelerate.tbtc', + }, }; function coinFixtures(coin: string): CoinToFixtures { @@ -99,7 +109,12 @@ export async function startMockBitgoServer(): Promise { /** Transaction prebuild — coin-specific fixture */ app.post('/api/v2/:coin/wallet/:walletId/tx/build', (req, res) => { - res.json(loadFixture(coinFixtures(req.params.coin).prebuildTx)); + const { coin } = req.params; + const isAccelerate = req.body?.cpfpTxIds?.length || req.body?.rbfTxIds?.length; + const fixtureName = isAccelerate + ? coinFixtures(coin).acceleratePrebuildTx + : coinFixtures(coin).prebuildTx; + res.json(loadFixture(fixtureName)); }); /** Transaction submit — coin-specific fixture */ From 10e0dcc7eca00e73860360147359ab9c6c2e3b0f Mon Sep 17 00:00:00 2001 From: Daniel Peng Date: Fri, 5 Jun 2026 10:33:30 -0400 Subject: [PATCH 2/2] test: edit integ tests for tx acceleration Ticket: WCN-768 --- .../integration/accelerate.integ.test.ts | 15 ++++++++++++--- .../integration/helpers/mockBitgoServer.ts | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/__tests__/integration/accelerate.integ.test.ts b/src/__tests__/integration/accelerate.integ.test.ts index d842dd4..f0eb63a 100644 --- a/src/__tests__/integration/accelerate.integ.test.ts +++ b/src/__tests__/integration/accelerate.integ.test.ts @@ -62,8 +62,12 @@ describe('Accelerate: EXTERNAL signing', () => { services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(1); services.keyProvider.calls.filter((c) => c.path === '/key').should.have.length(0); - /** BitGo must receive tx/build, block/latest, and tx/send */ - services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(1); + /** BitGo must receive tx/build with the correct cpfpTxIds, block/latest, and tx/send */ + const buildCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')); + buildCalls.should.have.length(1); + const buildBody = buildCalls[0].body as { cpfpTxIds?: string[] }; + buildBody.should.have.property('cpfpTxIds').which.deepEqual([CPFP_TX_ID]); + services.bitgo.calls .filter((c) => c.path.endsWith('/public/block/latest')) .should.have.length(1); @@ -127,7 +131,12 @@ describe('Accelerate: LOCAL signing', () => { services.keyProvider.calls.filter((c) => c.path === '/sign').should.have.length(0); services.keyProvider.calls.filter((c) => c.path.startsWith('/key/')).length.should.be.above(0); - services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')).should.have.length(1); + /** BitGo must receive tx/build with the correct cpfpTxIds, block/latest, and tx/send */ + const buildCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/tx/build')); + buildCalls.should.have.length(1); + const buildBody = buildCalls[0].body as { cpfpTxIds?: string[] }; + buildBody.should.have.property('cpfpTxIds').which.deepEqual([CPFP_TX_ID]); + services.bitgo.calls .filter((c) => c.path.endsWith('/public/block/latest')) .should.have.length(1); diff --git a/src/__tests__/integration/helpers/mockBitgoServer.ts b/src/__tests__/integration/helpers/mockBitgoServer.ts index dcafd90..4119a2b 100644 --- a/src/__tests__/integration/helpers/mockBitgoServer.ts +++ b/src/__tests__/integration/helpers/mockBitgoServer.ts @@ -25,7 +25,7 @@ type SendManyFixtureMethod = 'getWallet' | 'prebuildTx' | 'sendTx'; type SupportedCoin = 'hteth' | 'tbtc'; type CoinToFixtures = { [K in SendManyFixtureMethod]: `${K}.${C}`; -} & { acceleratePrebuildTx: string }; +} & { acceleratePrebuildTx: `prebuildTx.accelerate.${C}` | `prebuildTx.${C}` }; /** Registry — add a new coin here to support it across all sendMany integ test routes */ const COIN_FIXTURES: { [C in SupportedCoin]: CoinToFixtures } = {