Skip to content

Commit 0e94a4c

Browse files
Merge pull request #5265 from BitGo/BTC-1450.integrate-descriptor-parseTx.outputDifference
feat(abstract-utxo): add outputDifference utility
2 parents 037efc8 + 02f84bc commit 0e94a4c

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export type ComparableOutput = {
2+
script: Buffer;
3+
value: bigint;
4+
};
5+
6+
export type ParseOutputsResult<T extends ComparableOutput> = {
7+
/** These are the external outputs that were explicitly specified in the transaction */
8+
explicitExternalOutputs: T[];
9+
/**
10+
* These are the surprise external outputs that were not explicitly specified in the transaction.
11+
* They can be PayGo fees.
12+
*/
13+
implicitExternalOutputs: T[];
14+
/**
15+
* These are the outputs that were expected to be in the transaction but were not found.
16+
*/
17+
missingOutputs: T[];
18+
};
19+
20+
export function equalOutput<T extends ComparableOutput>(a: T, b: T): boolean {
21+
return a.value === b.value && a.script.equals(b.script);
22+
}
23+
24+
/**
25+
* @returns all outputs in the first array that are not in the second array
26+
* Outputs can occur more than once in each array.
27+
*/
28+
export function outputDifference<T extends ComparableOutput>(first: T[], second: T[]): T[] {
29+
first = first.slice();
30+
for (const output of second) {
31+
const index = first.findIndex((o) => equalOutput(o, output));
32+
if (index !== -1) {
33+
first.splice(index, 1);
34+
}
35+
}
36+
return first;
37+
}
38+
39+
/**
40+
* @param actualExternalOutputs - external outputs in the transaction
41+
* @param expectedExternalOutputs - external outputs that were expected to be in the transaction
42+
* @returns the difference between the actual and expected external outputs
43+
*/
44+
export function outputDifferencesWithExpected<T extends ComparableOutput>(
45+
actualExternalOutputs: T[],
46+
expectedExternalOutputs: T[]
47+
): ParseOutputsResult<T> {
48+
const implicitExternalOutputs = outputDifference(actualExternalOutputs, expectedExternalOutputs);
49+
const explicitExternalOutputs = outputDifference(actualExternalOutputs, implicitExternalOutputs);
50+
const missingOutputs = outputDifference(expectedExternalOutputs, actualExternalOutputs);
51+
return {
52+
explicitExternalOutputs,
53+
implicitExternalOutputs,
54+
missingOutputs,
55+
};
56+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import assert from 'assert';
2+
3+
import {
4+
ComparableOutput,
5+
equalOutput,
6+
outputDifference,
7+
outputDifferencesWithExpected,
8+
} from '../../../src/transaction/descriptor/outputDifference';
9+
10+
describe('outputDifference', function () {
11+
function output(script: string, value: bigint | number) {
12+
const scriptBuffer = Buffer.from(script, 'hex');
13+
if (scriptBuffer.toString('hex') !== script) {
14+
throw new Error('invalid script');
15+
}
16+
return {
17+
script: Buffer.from(script, 'hex'),
18+
value: BigInt(value),
19+
};
20+
}
21+
22+
const a = output('aa', 1);
23+
const a2 = output('aa', 2);
24+
const b = output('bb', 2);
25+
const c = output('cc', 3);
26+
27+
describe('equalOutput', function () {
28+
it('has expected result', function () {
29+
assert.deepStrictEqual(equalOutput(a, a), true);
30+
assert.deepStrictEqual(equalOutput(a, a2), false);
31+
assert.deepStrictEqual(equalOutput(a, b), false);
32+
});
33+
});
34+
35+
describe('outputDifference', function () {
36+
assert.deepStrictEqual(outputDifference([], []), []);
37+
assert.deepStrictEqual(outputDifference([a], []), [a]);
38+
assert.deepStrictEqual(outputDifference([], [a]), []);
39+
assert.deepStrictEqual(outputDifference([a], [a]), []);
40+
assert.deepStrictEqual(outputDifference([a, a], [a]), [a]);
41+
assert.deepStrictEqual(outputDifference([a, a, a], [a]), [a, a]);
42+
assert.deepStrictEqual(outputDifference([a, b, c], [a, b]), [c]);
43+
assert.deepStrictEqual(outputDifference([a, b, c, a], [a, b]), [c, a]);
44+
45+
assert.deepStrictEqual(outputDifference([a], [a2]), [a]);
46+
assert.deepStrictEqual(outputDifference([a2], [a]), [a2]);
47+
});
48+
49+
describe('outputDifferencesWithExpected', function () {
50+
function test(
51+
outputs: ComparableOutput[],
52+
recipients: ComparableOutput[],
53+
expected: {
54+
missing: ComparableOutput[];
55+
explicit: ComparableOutput[];
56+
implicit: ComparableOutput[];
57+
}
58+
) {
59+
const result = outputDifferencesWithExpected(outputs, recipients);
60+
assert.deepStrictEqual(result, {
61+
explicitExternalOutputs: expected.explicit,
62+
implicitExternalOutputs: expected.implicit,
63+
missingOutputs: expected.missing,
64+
});
65+
}
66+
67+
test([a], [], { missing: [], explicit: [], implicit: [a] });
68+
test([], [a], { missing: [a], explicit: [], implicit: [] });
69+
test([a], [a], { missing: [], explicit: [a], implicit: [] });
70+
test([a], [a2], { missing: [a2], explicit: [], implicit: [a] });
71+
test([b], [a], { missing: [a], explicit: [], implicit: [b] });
72+
test([a, a], [a], { missing: [], explicit: [a], implicit: [a] });
73+
test([a, b], [a], { missing: [], explicit: [a], implicit: [b] });
74+
});
75+
});

0 commit comments

Comments
 (0)