From 139197e545b9622e35e72b5b75a0a3b0a632a28b Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 26 Aug 2019 19:15:05 +0900 Subject: [PATCH 1/4] Add getFee and getVSize --- src/psbt.js | 56 ++++++++++++++++++++++++++++++----------- test/psbt.js | 10 ++++++++ ts_src/psbt.ts | 67 ++++++++++++++++++++++++++++++++++++++----------- types/psbt.d.ts | 2 ++ 4 files changed, 107 insertions(+), 28 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index e134774..d9ed3b1 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -153,6 +153,8 @@ class Psbt { if (input.nonWitnessUtxo) { addNonWitnessTxCache(this.__CACHE, input, inputIndex); } + c.__FEE = undefined; + c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -171,6 +173,8 @@ class Psbt { } const c = this.__CACHE; this.data.addOutput(outputData); + c.__FEE = undefined; + c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -187,20 +191,23 @@ class Psbt { return tx; } getFeeRate() { - if (!this.data.inputs.every(isFinalized)) - throw new Error('PSBT must be finalized to calculate fee rate'); - const c = this.__CACHE; - if (c.__FEE_RATE) return c.__FEE_RATE; - let tx; - let mustFinalize = true; - if (c.__EXTRACTED_TX) { - tx = c.__EXTRACTED_TX; - mustFinalize = false; - } else { - tx = c.__TX.clone(); - } - inputFinalizeGetAmts(this.data.inputs, tx, c, mustFinalize); - return c.__FEE_RATE; + return getTxCacheValue( + '__FEE_RATE', + 'fee rate', + this.data.inputs, + this.__CACHE, + ); + } + getFee() { + return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); + } + getVSize() { + return getTxCacheValue( + '__VSIZE', + 'virtual size', + this.data.inputs, + this.__CACHE, + ); } finalizeAllInputs() { utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one @@ -724,6 +731,25 @@ const checkWitnessScript = scriptCheckerFactory( payments.p2wsh, 'Witness script', ); +function getTxCacheValue(key, name, inputs, c) { + if (!inputs.every(isFinalized)) + throw new Error(`PSBT must be finalized to calculate ${name}`); + if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; + if (key === '__FEE' && c.__FEE) return c.__FEE; + if (key === '__VSIZE' && c.__VSIZE) return c.__VSIZE; + let tx; + let mustFinalize = true; + if (c.__EXTRACTED_TX) { + tx = c.__EXTRACTED_TX; + mustFinalize = false; + } else { + tx = c.__TX.clone(); + } + inputFinalizeGetAmts(inputs, tx, c, mustFinalize); + if (key === '__FEE_RATE') return c.__FEE_RATE; + else if (key === '__FEE') return c.__FEE; + else if (key === '__VSIZE') return c.__VSIZE; +} function getFinalScripts( script, scriptType, @@ -1124,6 +1150,8 @@ function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { throw new Error('Outputs are spending more than Inputs'); } const bytes = tx.virtualSize(); + cache.__VSIZE = bytes; + cache.__FEE = fee; cache.__EXTRACTED_TX = tx; cache.__FEE_RATE = Math.floor(fee / bytes); } diff --git a/test/psbt.js b/test/psbt.js index 467e426..4d4b3b5 100644 --- a/test/psbt.js +++ b/test/psbt.js @@ -149,6 +149,16 @@ describe(`Psbt`, () => { const fr1 = psbt5.getFeeRate() const fr2 = psbt5.getFeeRate() assert.strictEqual(fr1, fr2) + + const psbt6 = Psbt.fromBase64(f.psbt) + const f1 = psbt6.getFee() + const f2 = psbt6.getFee() + assert.strictEqual(f1, f2) + + const psbt7 = Psbt.fromBase64(f.psbt) + const vs1 = psbt7.getVSize() + const vs2 = psbt7.getVSize() + assert.strictEqual(vs1, vs2) }) }) }) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 0431056..333fa20 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -194,6 +194,8 @@ export class Psbt { if (input.nonWitnessUtxo) { addNonWitnessTxCache(this.__CACHE, input, inputIndex); } + c.__FEE = undefined; + c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -214,6 +216,8 @@ export class Psbt { } const c = this.__CACHE; this.data.addOutput(outputData); + c.__FEE = undefined; + c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -232,20 +236,25 @@ export class Psbt { } getFeeRate(): number { - if (!this.data.inputs.every(isFinalized)) - throw new Error('PSBT must be finalized to calculate fee rate'); - const c = this.__CACHE; - if (c.__FEE_RATE) return c.__FEE_RATE; - let tx: Transaction; - let mustFinalize = true; - if (c.__EXTRACTED_TX) { - tx = c.__EXTRACTED_TX; - mustFinalize = false; - } else { - tx = c.__TX.clone(); - } - inputFinalizeGetAmts(this.data.inputs, tx, c, mustFinalize); - return c.__FEE_RATE!; + return getTxCacheValue( + '__FEE_RATE', + 'fee rate', + this.data.inputs, + this.__CACHE, + )!; + } + + getFee(): number { + return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE)!; + } + + getVSize(): number { + return getTxCacheValue( + '__VSIZE', + 'virtual size', + this.data.inputs, + this.__CACHE, + )!; } finalizeAllInputs(): this { @@ -610,6 +619,8 @@ interface PsbtCache { __TX_IN_CACHE: { [index: string]: number }; __TX: Transaction; __FEE_RATE?: number; + __FEE?: number; + __VSIZE?: number; __EXTRACTED_TX?: Transaction; } @@ -920,6 +931,32 @@ const checkWitnessScript = scriptCheckerFactory( 'Witness script', ); +type TxCacheNumberKey = '__FEE_RATE' | '__FEE' | '__VSIZE'; +function getTxCacheValue( + key: TxCacheNumberKey, + name: string, + inputs: PsbtInput[], + c: PsbtCache, +): number | undefined { + if (!inputs.every(isFinalized)) + throw new Error(`PSBT must be finalized to calculate ${name}`); + if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; + if (key === '__FEE' && c.__FEE) return c.__FEE; + if (key === '__VSIZE' && c.__VSIZE) return c.__VSIZE; + let tx: Transaction; + let mustFinalize = true; + if (c.__EXTRACTED_TX) { + tx = c.__EXTRACTED_TX; + mustFinalize = false; + } else { + tx = c.__TX.clone(); + } + inputFinalizeGetAmts(inputs, tx, c, mustFinalize); + if (key === '__FEE_RATE') return c.__FEE_RATE!; + else if (key === '__FEE') return c.__FEE!; + else if (key === '__VSIZE') return c.__VSIZE!; +} + function getFinalScripts( script: Buffer, scriptType: string, @@ -1398,6 +1435,8 @@ function inputFinalizeGetAmts( throw new Error('Outputs are spending more than Inputs'); } const bytes = tx.virtualSize(); + cache.__VSIZE = bytes; + cache.__FEE = fee; cache.__EXTRACTED_TX = tx; cache.__FEE_RATE = Math.floor(fee / bytes); } diff --git a/types/psbt.d.ts b/types/psbt.d.ts index 6a56636..dddedd7 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -57,6 +57,8 @@ export declare class Psbt { addOutput(outputData: PsbtOutputExtended): this; extractTransaction(disableFeeCheck?: boolean): Transaction; getFeeRate(): number; + getFee(): number; + getVSize(): number; finalizeAllInputs(): this; finalizeInput(inputIndex: number): this; validateSignaturesOfAllInputs(): boolean; From 14d10c74a53b2500294d4b7c0f4f701da2656bbc Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 27 Aug 2019 10:06:43 +0900 Subject: [PATCH 2/4] Remove VSize, can get from Transaction --- src/psbt.js | 13 ------------- test/psbt.js | 5 ----- ts_src/psbt.ts | 17 +---------------- types/psbt.d.ts | 1 - 4 files changed, 1 insertion(+), 35 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index d9ed3b1..57a159c 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -154,7 +154,6 @@ class Psbt { addNonWitnessTxCache(this.__CACHE, input, inputIndex); } c.__FEE = undefined; - c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -174,7 +173,6 @@ class Psbt { const c = this.__CACHE; this.data.addOutput(outputData); c.__FEE = undefined; - c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -201,14 +199,6 @@ class Psbt { getFee() { return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); } - getVSize() { - return getTxCacheValue( - '__VSIZE', - 'virtual size', - this.data.inputs, - this.__CACHE, - ); - } finalizeAllInputs() { utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); @@ -736,7 +726,6 @@ function getTxCacheValue(key, name, inputs, c) { throw new Error(`PSBT must be finalized to calculate ${name}`); if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; if (key === '__FEE' && c.__FEE) return c.__FEE; - if (key === '__VSIZE' && c.__VSIZE) return c.__VSIZE; let tx; let mustFinalize = true; if (c.__EXTRACTED_TX) { @@ -748,7 +737,6 @@ function getTxCacheValue(key, name, inputs, c) { inputFinalizeGetAmts(inputs, tx, c, mustFinalize); if (key === '__FEE_RATE') return c.__FEE_RATE; else if (key === '__FEE') return c.__FEE; - else if (key === '__VSIZE') return c.__VSIZE; } function getFinalScripts( script, @@ -1150,7 +1138,6 @@ function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { throw new Error('Outputs are spending more than Inputs'); } const bytes = tx.virtualSize(); - cache.__VSIZE = bytes; cache.__FEE = fee; cache.__EXTRACTED_TX = tx; cache.__FEE_RATE = Math.floor(fee / bytes); diff --git a/test/psbt.js b/test/psbt.js index 4d4b3b5..54eb5e4 100644 --- a/test/psbt.js +++ b/test/psbt.js @@ -154,11 +154,6 @@ describe(`Psbt`, () => { const f1 = psbt6.getFee() const f2 = psbt6.getFee() assert.strictEqual(f1, f2) - - const psbt7 = Psbt.fromBase64(f.psbt) - const vs1 = psbt7.getVSize() - const vs2 = psbt7.getVSize() - assert.strictEqual(vs1, vs2) }) }) }) diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 333fa20..73f7cfc 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -195,7 +195,6 @@ export class Psbt { addNonWitnessTxCache(this.__CACHE, input, inputIndex); } c.__FEE = undefined; - c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -217,7 +216,6 @@ export class Psbt { const c = this.__CACHE; this.data.addOutput(outputData); c.__FEE = undefined; - c.__VSIZE = undefined; c.__FEE_RATE = undefined; c.__EXTRACTED_TX = undefined; return this; @@ -248,15 +246,6 @@ export class Psbt { return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE)!; } - getVSize(): number { - return getTxCacheValue( - '__VSIZE', - 'virtual size', - this.data.inputs, - this.__CACHE, - )!; - } - finalizeAllInputs(): this { checkForInput(this.data.inputs, 0); // making sure we have at least one range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); @@ -620,7 +609,6 @@ interface PsbtCache { __TX: Transaction; __FEE_RATE?: number; __FEE?: number; - __VSIZE?: number; __EXTRACTED_TX?: Transaction; } @@ -931,7 +919,7 @@ const checkWitnessScript = scriptCheckerFactory( 'Witness script', ); -type TxCacheNumberKey = '__FEE_RATE' | '__FEE' | '__VSIZE'; +type TxCacheNumberKey = '__FEE_RATE' | '__FEE'; function getTxCacheValue( key: TxCacheNumberKey, name: string, @@ -942,7 +930,6 @@ function getTxCacheValue( throw new Error(`PSBT must be finalized to calculate ${name}`); if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; if (key === '__FEE' && c.__FEE) return c.__FEE; - if (key === '__VSIZE' && c.__VSIZE) return c.__VSIZE; let tx: Transaction; let mustFinalize = true; if (c.__EXTRACTED_TX) { @@ -954,7 +941,6 @@ function getTxCacheValue( inputFinalizeGetAmts(inputs, tx, c, mustFinalize); if (key === '__FEE_RATE') return c.__FEE_RATE!; else if (key === '__FEE') return c.__FEE!; - else if (key === '__VSIZE') return c.__VSIZE!; } function getFinalScripts( @@ -1435,7 +1421,6 @@ function inputFinalizeGetAmts( throw new Error('Outputs are spending more than Inputs'); } const bytes = tx.virtualSize(); - cache.__VSIZE = bytes; cache.__FEE = fee; cache.__EXTRACTED_TX = tx; cache.__FEE_RATE = Math.floor(fee / bytes); diff --git a/types/psbt.d.ts b/types/psbt.d.ts index dddedd7..a0c669d 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -58,7 +58,6 @@ export declare class Psbt { extractTransaction(disableFeeCheck?: boolean): Transaction; getFeeRate(): number; getFee(): number; - getVSize(): number; finalizeAllInputs(): this; finalizeInput(inputIndex: number): this; validateSignaturesOfAllInputs(): boolean; From 77e068c388bdfe30a60b1a6b3fe3a7d60b494fe4 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 27 Aug 2019 10:13:18 +0900 Subject: [PATCH 3/4] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e23430..37e097f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 5.1.5 +__added__ +- `Psbt` now has `getFee(): number` for use when all inputs are finalized. It returns the satoshi fee of the transaction. Calling getFee, getFeeRate, or extractTransaction will cache these values so if you call one after the other, the second call will return immediately. + # 5.1.4 __changed__ - `Psbt` inputs using segwit scripts can now work with nonWitnessUtxo as well as the original witnessUtxo. The reasoning for this is that nonWitnessUtxo has all the information contained in the witnessUtxo, so rejecting signing even though we have all the info we need is unnecessary. Trying to sign a non-segwit script with a witnessUtxo will still throw an Error as it should. From 5bbf255b29dd1a0535b68790496b0dbd8b305e66 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 27 Aug 2019 10:13:24 +0900 Subject: [PATCH 4/4] 5.1.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb292ce..c2966f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "5.1.4", + "version": "5.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 07bb419..d701877 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib", - "version": "5.1.4", + "version": "5.1.5", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", "types": "./types/index.d.ts",