Refactor: input finalize and get fee shared logic

This commit is contained in:
junderw 2019-07-09 12:15:20 +09:00
parent 497d048ebf
commit 9749a216b8
No known key found for this signature in database
GPG key ID: B256185D3A971908
2 changed files with 120 additions and 96 deletions

View file

@ -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' ||

View file

@ -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' ||