From 9749a216b8cc68fff6e00771cf93af23ce47c52a Mon Sep 17 00:00:00 2001
From: junderw <junderwood@bitcoinbank.co.jp>
Date: Tue, 9 Jul 2019 12:15:20 +0900
Subject: [PATCH] Refactor: input finalize and get fee shared logic

---
 src/psbt.js    | 104 ++++++++++++++++++++++++---------------------
 ts_src/psbt.ts | 112 ++++++++++++++++++++++++++++---------------------
 2 files changed, 120 insertions(+), 96 deletions(-)

diff --git a/src/psbt.js b/src/psbt.js
index eff98ae..c2f9bd3 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -164,69 +164,42 @@ class Psbt extends bip174_1.Psbt {
   }
   extractTransaction(disableFeeCheck) {
     if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
+    const c = this.__CACHE;
     if (!disableFeeCheck) {
-      const feeRate = this.__CACHE.__FEE_RATE || this.getFeeRate();
-      const vsize = this.__CACHE.__EXTRACTED_TX.virtualSize();
-      const satoshis = feeRate * vsize;
-      if (feeRate >= this.opts.maximumFeeRate) {
-        throw new Error(
-          `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
-            `fees, which is ${feeRate} satoshi per byte for a transaction ` +
-            `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
-            `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
-            `pass true to the first arg of extractTransaction.`,
-        );
-      }
+      checkFees(this, c, this.opts);
     }
-    if (this.__CACHE.__EXTRACTED_TX) return this.__CACHE.__EXTRACTED_TX;
-    const tx = this.__CACHE.__TX.clone();
-    this.inputs.forEach((input, idx) => {
-      if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
-      if (input.finalScriptWitness) {
-        tx.ins[idx].witness = scriptWitnessToWitnessStack(
-          input.finalScriptWitness,
-        );
-      }
-    });
-    this.__CACHE.__EXTRACTED_TX = tx;
+    if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
+    const tx = c.__TX.clone();
+    inputFinalizeGetAmts(this.inputs, tx, c, true, false);
+    c.__EXTRACTED_TX = tx;
     return tx;
   }
   getFeeRate() {
     if (!this.inputs.every(isFinalized))
       throw new Error('PSBT must be finalized to calculate fee rate');
-    if (this.__CACHE.__FEE_RATE) return this.__CACHE.__FEE_RATE;
+    const c = this.__CACHE;
+    if (c.__FEE_RATE) return c.__FEE_RATE;
     let tx;
-    let inputAmount = 0;
     let mustFinalize = true;
-    if (this.__CACHE.__EXTRACTED_TX) {
-      tx = this.__CACHE.__EXTRACTED_TX;
+    if (c.__EXTRACTED_TX) {
+      tx = c.__EXTRACTED_TX;
       mustFinalize = false;
     } else {
-      tx = this.__CACHE.__TX.clone();
+      tx = c.__TX.clone();
     }
-    this.inputs.forEach((input, idx) => {
-      if (mustFinalize && input.finalScriptSig)
-        tx.ins[idx].script = input.finalScriptSig;
-      if (mustFinalize && input.finalScriptWitness) {
-        tx.ins[idx].witness = scriptWitnessToWitnessStack(
-          input.finalScriptWitness,
-        );
-      }
-      if (input.witnessUtxo) {
-        inputAmount += input.witnessUtxo.value;
-      } else if (input.nonWitnessUtxo) {
-        const nwTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, idx);
-        const vout = this.__CACHE.__TX.ins[idx].index;
-        const out = nwTx.outs[vout];
-        inputAmount += out.value;
-      }
-    });
-    this.__CACHE.__EXTRACTED_TX = tx;
+    const inputAmount = inputFinalizeGetAmts(
+      this.inputs,
+      tx,
+      c,
+      mustFinalize,
+      true,
+    );
+    c.__EXTRACTED_TX = tx;
     const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0);
     const fee = inputAmount - outputAmount;
     const bytes = tx.virtualSize();
-    this.__CACHE.__FEE_RATE = Math.floor(fee / bytes);
-    return this.__CACHE.__FEE_RATE;
+    c.__FEE_RATE = Math.floor(fee / bytes);
+    return c.__FEE_RATE;
   }
   finalizeAllInputs() {
     const inputResults = range(this.inputs.length).map(idx =>
@@ -859,6 +832,41 @@ function getOutputAdder(cache) {
     return selfCache.__TX.toBuffer();
   };
 }
+function checkFees(psbt, cache, opts) {
+  const feeRate = cache.__FEE_RATE || psbt.getFeeRate();
+  const vsize = cache.__EXTRACTED_TX.virtualSize();
+  const satoshis = feeRate * vsize;
+  if (feeRate >= opts.maximumFeeRate) {
+    throw new Error(
+      `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
+        `fees, which is ${feeRate} satoshi per byte for a transaction ` +
+        `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
+        `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
+        `pass true to the first arg of extractTransaction.`,
+    );
+  }
+}
+function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize, getAmounts) {
+  let inputAmount = 0;
+  inputs.forEach((input, idx) => {
+    if (mustFinalize && input.finalScriptSig)
+      tx.ins[idx].script = input.finalScriptSig;
+    if (mustFinalize && input.finalScriptWitness) {
+      tx.ins[idx].witness = scriptWitnessToWitnessStack(
+        input.finalScriptWitness,
+      );
+    }
+    if (getAmounts && input.witnessUtxo) {
+      inputAmount += input.witnessUtxo.value;
+    } else if (getAmounts && input.nonWitnessUtxo) {
+      const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx);
+      const vout = tx.ins[idx].index;
+      const out = nwTx.outs[vout];
+      inputAmount += out.value;
+    }
+  });
+  return inputAmount;
+}
 function check32Bit(num) {
   if (
     typeof num !== 'number' ||
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index 370b481..0771436 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -204,73 +204,46 @@ export class Psbt extends PsbtBase {
 
   extractTransaction(disableFeeCheck?: boolean): Transaction {
     if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
+    const c = this.__CACHE;
     if (!disableFeeCheck) {
-      const feeRate = this.__CACHE.__FEE_RATE || this.getFeeRate();
-      const vsize = this.__CACHE.__EXTRACTED_TX!.virtualSize();
-      const satoshis = feeRate * vsize;
-      if (feeRate >= this.opts.maximumFeeRate) {
-        throw new Error(
-          `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
-            `fees, which is ${feeRate} satoshi per byte for a transaction ` +
-            `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
-            `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
-            `pass true to the first arg of extractTransaction.`,
-        );
-      }
+      checkFees(this, c, this.opts);
     }
-    if (this.__CACHE.__EXTRACTED_TX) return this.__CACHE.__EXTRACTED_TX;
-    const tx = this.__CACHE.__TX.clone();
-    this.inputs.forEach((input, idx) => {
-      if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
-      if (input.finalScriptWitness) {
-        tx.ins[idx].witness = scriptWitnessToWitnessStack(
-          input.finalScriptWitness,
-        );
-      }
-    });
-    this.__CACHE.__EXTRACTED_TX = tx;
+    if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
+    const tx = c.__TX.clone();
+    inputFinalizeGetAmts(this.inputs, tx, c, true, false);
+    c.__EXTRACTED_TX = tx;
     return tx;
   }
 
   getFeeRate(): number {
     if (!this.inputs.every(isFinalized))
       throw new Error('PSBT must be finalized to calculate fee rate');
-    if (this.__CACHE.__FEE_RATE) return this.__CACHE.__FEE_RATE;
+    const c = this.__CACHE;
+    if (c.__FEE_RATE) return c.__FEE_RATE;
     let tx: Transaction;
-    let inputAmount = 0;
     let mustFinalize = true;
-    if (this.__CACHE.__EXTRACTED_TX) {
-      tx = this.__CACHE.__EXTRACTED_TX;
+    if (c.__EXTRACTED_TX) {
+      tx = c.__EXTRACTED_TX;
       mustFinalize = false;
     } else {
-      tx = this.__CACHE.__TX.clone();
+      tx = c.__TX.clone();
     }
-    this.inputs.forEach((input, idx) => {
-      if (mustFinalize && input.finalScriptSig)
-        tx.ins[idx].script = input.finalScriptSig;
-      if (mustFinalize && input.finalScriptWitness) {
-        tx.ins[idx].witness = scriptWitnessToWitnessStack(
-          input.finalScriptWitness,
-        );
-      }
-      if (input.witnessUtxo) {
-        inputAmount += input.witnessUtxo.value;
-      } else if (input.nonWitnessUtxo) {
-        const nwTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, idx);
-        const vout = this.__CACHE.__TX.ins[idx].index;
-        const out = nwTx.outs[vout] as Output;
-        inputAmount += out.value;
-      }
-    });
-    this.__CACHE.__EXTRACTED_TX = tx;
+    const inputAmount = inputFinalizeGetAmts(
+      this.inputs,
+      tx,
+      c,
+      mustFinalize,
+      true,
+    );
+    c.__EXTRACTED_TX = tx;
     const outputAmount = (tx.outs as Output[]).reduce(
       (total, o) => total + o.value,
       0,
     );
     const fee = inputAmount - outputAmount;
     const bytes = tx.virtualSize();
-    this.__CACHE.__FEE_RATE = Math.floor(fee / bytes);
-    return this.__CACHE.__FEE_RATE;
+    c.__FEE_RATE = Math.floor(fee / bytes);
+    return c.__FEE_RATE;
   }
 
   finalizeAllInputs(): {
@@ -1075,6 +1048,49 @@ function getOutputAdder(
   };
 }
 
+function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void {
+  const feeRate = cache.__FEE_RATE || psbt.getFeeRate();
+  const vsize = cache.__EXTRACTED_TX!.virtualSize();
+  const satoshis = feeRate * vsize;
+  if (feeRate >= opts.maximumFeeRate) {
+    throw new Error(
+      `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
+        `fees, which is ${feeRate} satoshi per byte for a transaction ` +
+        `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
+        `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
+        `pass true to the first arg of extractTransaction.`,
+    );
+  }
+}
+
+function inputFinalizeGetAmts(
+  inputs: PsbtInput[],
+  tx: Transaction,
+  cache: PsbtCache,
+  mustFinalize: boolean,
+  getAmounts: boolean,
+): number {
+  let inputAmount = 0;
+  inputs.forEach((input, idx) => {
+    if (mustFinalize && input.finalScriptSig)
+      tx.ins[idx].script = input.finalScriptSig;
+    if (mustFinalize && input.finalScriptWitness) {
+      tx.ins[idx].witness = scriptWitnessToWitnessStack(
+        input.finalScriptWitness,
+      );
+    }
+    if (getAmounts && input.witnessUtxo) {
+      inputAmount += input.witnessUtxo.value;
+    } else if (getAmounts && input.nonWitnessUtxo) {
+      const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx);
+      const vout = tx.ins[idx].index;
+      const out = nwTx.outs[vout] as Output;
+      inputAmount += out.value;
+    }
+  });
+  return inputAmount;
+}
+
 function check32Bit(num: number): void {
   if (
     typeof num !== 'number' ||