Skip to content

Commit bf51d1d

Browse files
feat(utxo-core): add PSBT virtual size estimation
Add helper function to estimate virtual size for PSBT transactions using descriptor map Issue: BTC-1826
1 parent f3ace39 commit bf51d1d

File tree

2 files changed

+48
-5
lines changed

2 files changed

+48
-5
lines changed

modules/utxo-core/src/descriptor/VirtualSize.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import * as utxolib from '@bitgo/utxo-lib';
12
import { Dimensions, VirtualSizes } from '@bitgo/unspents';
23
import { Descriptor } from '@bitgo/wasm-miniscript';
34

45
import { DescriptorMap } from './DescriptorMap';
6+
import { findDescriptorForInput } from './psbt';
57

68
function getScriptPubKeyLength(descType: string): number {
79
// See https://bitcoinops.org/en/tools/calc-size/
@@ -105,3 +107,15 @@ export function getVirtualSize(
105107
// we will just assume that we have at least one segwit input
106108
return inputVSize + outputVSize + VirtualSizes.txSegOverheadVSize;
107109
}
110+
111+
export function getVirtualSizeEstimateForPsbt(psbt: utxolib.Psbt, descriptorMap: DescriptorMap): number {
112+
const inputs = psbt.data.inputs.map((i) => {
113+
const result = findDescriptorForInput(i, descriptorMap);
114+
if (!result) {
115+
throw new Error('Could not find descriptor for input');
116+
}
117+
return result.descriptor;
118+
});
119+
const outputs = psbt.txOutputs.map((o) => ({ script: o.script }));
120+
return getVirtualSize({ inputs, outputs });
121+
}

modules/utxo-core/test/descriptor/psbt/VirtualSize.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ import {
44
getChangeOutputVSizesForDescriptor,
55
getInputVSizesForDescriptors,
66
getVirtualSize,
7+
getVirtualSizeEstimateForPsbt,
78
} from '../../../src/descriptor/VirtualSize';
8-
import { DescriptorTemplate, getDescriptor, getDescriptorMap } from '../../../src/testutil/descriptor';
9+
import {
10+
DescriptorTemplate,
11+
getDefaultXPubs,
12+
getDescriptor,
13+
getDescriptorMap,
14+
mockPsbtDefault,
15+
} from '../../../src/testutil/descriptor';
16+
import { getKeyTriple } from '../../../src/testutil';
17+
import { finalizePsbt } from '../../../src/descriptor';
918

1019
describe('VirtualSize', function () {
1120
describe('getInputVSizesForDescriptorWallet', function () {
@@ -45,11 +54,11 @@ describe('VirtualSize', function () {
4554
getVirtualSize(
4655
{
4756
inputs: [{ descriptorName: 'internal' }],
48-
outputs: [{ script: Buffer.alloc(32) }],
57+
outputs: [{ script: Buffer.alloc(34) }],
4958
},
5059
getDescriptorMap(t)
5160
),
52-
outputSize
61+
inputSize + outputSize + 11
5362
);
5463

5564
const descriptor = getDescriptor(t);
@@ -65,8 +74,28 @@ describe('VirtualSize', function () {
6574
);
6675
});
6776
});
77+
78+
describe(`getVirtualSizeForPsbt ${t}`, function () {
79+
const keys = getKeyTriple('a');
80+
const descriptorSelf = getDescriptor(
81+
t,
82+
keys.map((k) => k.neutered().toBase58())
83+
);
84+
const descriptorOther = getDescriptor(t, getDefaultXPubs('b'));
85+
it('returns expected virtual size', function () {
86+
const psbt = mockPsbtDefault({ descriptorSelf, descriptorOther });
87+
const descriptorMap = new Map([['internal', descriptorSelf]]);
88+
const expectedVirtualSize = inputSize * 2 + outputSize * 2 + 11;
89+
assert.deepStrictEqual(getVirtualSizeEstimateForPsbt(psbt, descriptorMap), expectedVirtualSize);
90+
psbt.signAllInputsHD(keys[0]);
91+
psbt.signAllInputsHD(keys[1]);
92+
finalizePsbt(psbt);
93+
// TODO(BTC-1797): figure out why we overestimate by 1
94+
assert.strictEqual(psbt.extractTransaction().virtualSize(), expectedVirtualSize - 1);
95+
});
96+
});
6897
}
6998

70-
describeWithTemplate('Wsh2Of3', 105, 157);
71-
describeWithTemplate('Tr2Of3-NoKeyPath', 109, 161);
99+
describeWithTemplate('Wsh2Of3', 105, 43);
100+
describeWithTemplate('Tr2Of3-NoKeyPath', 109, 43);
72101
});

0 commit comments

Comments
 (0)