Refactor: cache
This commit is contained in:
parent
88de1e7b0e
commit
e4e5111376
4 changed files with 171 additions and 163 deletions
149
src/psbt.js
149
src/psbt.js
|
@ -22,10 +22,13 @@ class Psbt extends bip174_1.Psbt {
|
||||||
__NON_WITNESS_UTXO_TX_CACHE: [],
|
__NON_WITNESS_UTXO_TX_CACHE: [],
|
||||||
__NON_WITNESS_UTXO_BUF_CACHE: [],
|
__NON_WITNESS_UTXO_BUF_CACHE: [],
|
||||||
__TX_IN_CACHE: {},
|
__TX_IN_CACHE: {},
|
||||||
|
__TX: new transaction_1.Transaction(),
|
||||||
};
|
};
|
||||||
// set defaults
|
// set defaults
|
||||||
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
||||||
this.__TX = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
|
this.__CACHE.__TX = transaction_1.Transaction.fromBuffer(
|
||||||
|
this.globalMap.unsignedTx,
|
||||||
|
);
|
||||||
this.setVersion(2);
|
this.setVersion(2);
|
||||||
// set cache
|
// set cache
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -33,15 +36,15 @@ class Psbt extends bip174_1.Psbt {
|
||||||
Object.defineProperty(this.globalMap, 'unsignedTx', {
|
Object.defineProperty(this.globalMap, 'unsignedTx', {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get() {
|
get() {
|
||||||
if (self.__TX_BUF_CACHE !== undefined) {
|
if (self.__CACHE.__TX_BUF_CACHE !== undefined) {
|
||||||
return self.__TX_BUF_CACHE;
|
return self.__CACHE.__TX_BUF_CACHE;
|
||||||
} else {
|
} else {
|
||||||
self.__TX_BUF_CACHE = self.__TX.toBuffer();
|
self.__CACHE.__TX_BUF_CACHE = self.__CACHE.__TX.toBuffer();
|
||||||
return self.__TX_BUF_CACHE;
|
return self.__CACHE.__TX_BUF_CACHE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set(data) {
|
set(data) {
|
||||||
self.__TX_BUF_CACHE = data;
|
self.__CACHE.__TX_BUF_CACHE = data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// Make data hidden when enumerating
|
// Make data hidden when enumerating
|
||||||
|
@ -52,8 +55,6 @@ class Psbt extends bip174_1.Psbt {
|
||||||
});
|
});
|
||||||
dpew(this, '__TX', false, true);
|
dpew(this, '__TX', false, true);
|
||||||
dpew(this, '__EXTRACTED_TX', false, true);
|
dpew(this, '__EXTRACTED_TX', false, true);
|
||||||
dpew(this, '__FEE_RATE', false, true);
|
|
||||||
dpew(this, '__TX_BUF_CACHE', false, true);
|
|
||||||
dpew(this, '__CACHE', false, true);
|
dpew(this, '__CACHE', false, true);
|
||||||
dpew(this, 'opts', false, true);
|
dpew(this, 'opts', false, true);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +62,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
const tx = transaction_1.Transaction.fromBuffer(txBuf);
|
const tx = transaction_1.Transaction.fromBuffer(txBuf);
|
||||||
checkTxEmpty(tx);
|
checkTxEmpty(tx);
|
||||||
const psbt = new this();
|
const psbt = new this();
|
||||||
psbt.__TX = tx;
|
psbt.__CACHE.__TX = tx;
|
||||||
checkTxForDupeIns(tx, psbt.__CACHE);
|
checkTxForDupeIns(tx, psbt.__CACHE);
|
||||||
let inputCount = tx.ins.length;
|
let inputCount = tx.ins.length;
|
||||||
let outputCount = tx.outs.length;
|
let outputCount = tx.outs.length;
|
||||||
|
@ -90,7 +91,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const psbt = super.fromBuffer(buffer, txCountGetter);
|
const psbt = super.fromBuffer(buffer, txCountGetter);
|
||||||
psbt.__TX = tx;
|
psbt.__CACHE.__TX = tx;
|
||||||
checkTxForDupeIns(tx, psbt.__CACHE);
|
checkTxForDupeIns(tx, psbt.__CACHE);
|
||||||
return psbt;
|
return psbt;
|
||||||
}
|
}
|
||||||
|
@ -104,63 +105,39 @@ class Psbt extends bip174_1.Psbt {
|
||||||
setVersion(version) {
|
setVersion(version) {
|
||||||
check32Bit(version);
|
check32Bit(version);
|
||||||
checkInputsForPartialSig(this.inputs, 'setVersion');
|
checkInputsForPartialSig(this.inputs, 'setVersion');
|
||||||
this.__TX.version = version;
|
const c = this.__CACHE;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
c.__TX.version = version;
|
||||||
this.__EXTRACTED_TX = undefined;
|
c.__TX_BUF_CACHE = undefined;
|
||||||
|
c.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
setLocktime(locktime) {
|
setLocktime(locktime) {
|
||||||
check32Bit(locktime);
|
check32Bit(locktime);
|
||||||
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
||||||
this.__TX.locktime = locktime;
|
const c = this.__CACHE;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
c.__TX.locktime = locktime;
|
||||||
this.__EXTRACTED_TX = undefined;
|
c.__TX_BUF_CACHE = undefined;
|
||||||
|
c.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
setSequence(inputIndex, sequence) {
|
setSequence(inputIndex, sequence) {
|
||||||
check32Bit(sequence);
|
check32Bit(sequence);
|
||||||
checkInputsForPartialSig(this.inputs, 'setSequence');
|
checkInputsForPartialSig(this.inputs, 'setSequence');
|
||||||
if (this.__TX.ins.length <= inputIndex) {
|
const c = this.__CACHE;
|
||||||
|
if (c.__TX.ins.length <= inputIndex) {
|
||||||
throw new Error('Input index too high');
|
throw new Error('Input index too high');
|
||||||
}
|
}
|
||||||
this.__TX.ins[inputIndex].sequence = sequence;
|
c.__TX.ins[inputIndex].sequence = sequence;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
c.__TX_BUF_CACHE = undefined;
|
||||||
this.__EXTRACTED_TX = undefined;
|
c.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
addInput(inputData) {
|
addInput(inputData) {
|
||||||
checkInputsForPartialSig(this.inputs, 'addInput');
|
checkInputsForPartialSig(this.inputs, 'addInput');
|
||||||
const self = this;
|
const inputAdder = getInputAdder(this.__CACHE);
|
||||||
const inputAdder = (_inputData, txBuf) => {
|
|
||||||
if (
|
|
||||||
!txBuf ||
|
|
||||||
_inputData.hash === undefined ||
|
|
||||||
_inputData.index === undefined ||
|
|
||||||
(!Buffer.isBuffer(_inputData.hash) &&
|
|
||||||
typeof _inputData.hash !== 'string') ||
|
|
||||||
typeof _inputData.index !== 'number'
|
|
||||||
) {
|
|
||||||
throw new Error('Error adding input.');
|
|
||||||
}
|
|
||||||
const prevHash = Buffer.isBuffer(_inputData.hash)
|
|
||||||
? _inputData.hash
|
|
||||||
: bufferutils_1.reverseBuffer(Buffer.from(_inputData.hash, 'hex'));
|
|
||||||
// Check if input already exists in cache.
|
|
||||||
const input = { hash: prevHash, index: _inputData.index };
|
|
||||||
checkTxInputCache(self.__CACHE, input);
|
|
||||||
self.__TX.ins.push(
|
|
||||||
Object.assign({}, input, {
|
|
||||||
script: Buffer.alloc(0),
|
|
||||||
sequence:
|
|
||||||
_inputData.sequence || transaction_1.Transaction.DEFAULT_SEQUENCE,
|
|
||||||
witness: [],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return self.__TX.toBuffer();
|
|
||||||
};
|
|
||||||
super.addInput(inputData, inputAdder);
|
super.addInput(inputData, inputAdder);
|
||||||
this.__FEE_RATE = undefined;
|
this.__CACHE.__FEE_RATE = undefined;
|
||||||
this.__EXTRACTED_TX = undefined;
|
this.__CACHE.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
addOutput(outputData) {
|
addOutput(outputData) {
|
||||||
|
@ -182,15 +159,15 @@ class Psbt extends bip174_1.Psbt {
|
||||||
) {
|
) {
|
||||||
throw new Error('Error adding output.');
|
throw new Error('Error adding output.');
|
||||||
}
|
}
|
||||||
self.__TX.outs.push({
|
self.__CACHE.__TX.outs.push({
|
||||||
script: _outputData.script,
|
script: _outputData.script,
|
||||||
value: _outputData.value,
|
value: _outputData.value,
|
||||||
});
|
});
|
||||||
return self.__TX.toBuffer();
|
return self.__CACHE.__TX.toBuffer();
|
||||||
};
|
};
|
||||||
super.addOutput(outputData, true, outputAdder);
|
super.addOutput(outputData, true, outputAdder);
|
||||||
this.__FEE_RATE = undefined;
|
this.__CACHE.__FEE_RATE = undefined;
|
||||||
this.__EXTRACTED_TX = undefined;
|
this.__CACHE.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) {
|
addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) {
|
||||||
|
@ -202,8 +179,8 @@ class Psbt extends bip174_1.Psbt {
|
||||||
extractTransaction(disableFeeCheck) {
|
extractTransaction(disableFeeCheck) {
|
||||||
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
||||||
if (!disableFeeCheck) {
|
if (!disableFeeCheck) {
|
||||||
const feeRate = this.__FEE_RATE || this.getFeeRate();
|
const feeRate = this.__CACHE.__FEE_RATE || this.getFeeRate();
|
||||||
const vsize = this.__EXTRACTED_TX.virtualSize();
|
const vsize = this.__CACHE.__EXTRACTED_TX.virtualSize();
|
||||||
const satoshis = feeRate * vsize;
|
const satoshis = feeRate * vsize;
|
||||||
if (feeRate >= this.opts.maximumFeeRate) {
|
if (feeRate >= this.opts.maximumFeeRate) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -215,8 +192,8 @@ class Psbt extends bip174_1.Psbt {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.__EXTRACTED_TX) return this.__EXTRACTED_TX;
|
if (this.__CACHE.__EXTRACTED_TX) return this.__CACHE.__EXTRACTED_TX;
|
||||||
const tx = this.__TX.clone();
|
const tx = this.__CACHE.__TX.clone();
|
||||||
this.inputs.forEach((input, idx) => {
|
this.inputs.forEach((input, idx) => {
|
||||||
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
||||||
if (input.finalScriptWitness) {
|
if (input.finalScriptWitness) {
|
||||||
|
@ -225,21 +202,21 @@ class Psbt extends bip174_1.Psbt {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.__EXTRACTED_TX = tx;
|
this.__CACHE.__EXTRACTED_TX = tx;
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
getFeeRate() {
|
getFeeRate() {
|
||||||
if (!this.inputs.every(isFinalized))
|
if (!this.inputs.every(isFinalized))
|
||||||
throw new Error('PSBT must be finalized to calculate fee rate');
|
throw new Error('PSBT must be finalized to calculate fee rate');
|
||||||
if (this.__FEE_RATE) return this.__FEE_RATE;
|
if (this.__CACHE.__FEE_RATE) return this.__CACHE.__FEE_RATE;
|
||||||
let tx;
|
let tx;
|
||||||
let inputAmount = 0;
|
let inputAmount = 0;
|
||||||
let mustFinalize = true;
|
let mustFinalize = true;
|
||||||
if (this.__EXTRACTED_TX) {
|
if (this.__CACHE.__EXTRACTED_TX) {
|
||||||
tx = this.__EXTRACTED_TX;
|
tx = this.__CACHE.__EXTRACTED_TX;
|
||||||
mustFinalize = false;
|
mustFinalize = false;
|
||||||
} else {
|
} else {
|
||||||
tx = this.__TX.clone();
|
tx = this.__CACHE.__TX.clone();
|
||||||
}
|
}
|
||||||
this.inputs.forEach((input, idx) => {
|
this.inputs.forEach((input, idx) => {
|
||||||
if (mustFinalize && input.finalScriptSig)
|
if (mustFinalize && input.finalScriptSig)
|
||||||
|
@ -253,17 +230,17 @@ class Psbt extends bip174_1.Psbt {
|
||||||
inputAmount += input.witnessUtxo.value;
|
inputAmount += input.witnessUtxo.value;
|
||||||
} else if (input.nonWitnessUtxo) {
|
} else if (input.nonWitnessUtxo) {
|
||||||
const nwTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, idx);
|
const nwTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, idx);
|
||||||
const vout = this.__TX.ins[idx].index;
|
const vout = this.__CACHE.__TX.ins[idx].index;
|
||||||
const out = nwTx.outs[vout];
|
const out = nwTx.outs[vout];
|
||||||
inputAmount += out.value;
|
inputAmount += out.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.__EXTRACTED_TX = tx;
|
this.__CACHE.__EXTRACTED_TX = tx;
|
||||||
const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0);
|
const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0);
|
||||||
const fee = inputAmount - outputAmount;
|
const fee = inputAmount - outputAmount;
|
||||||
const bytes = tx.virtualSize();
|
const bytes = tx.virtualSize();
|
||||||
this.__FEE_RATE = Math.floor(fee / bytes);
|
this.__CACHE.__FEE_RATE = Math.floor(fee / bytes);
|
||||||
return this.__FEE_RATE;
|
return this.__CACHE.__FEE_RATE;
|
||||||
}
|
}
|
||||||
finalizeAllInputs() {
|
finalizeAllInputs() {
|
||||||
const inputResults = range(this.inputs.length).map(idx =>
|
const inputResults = range(this.inputs.length).map(idx =>
|
||||||
|
@ -280,7 +257,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
);
|
);
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
|
@ -322,7 +299,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
? getHashForSig(
|
? getHashForSig(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
Object.assign({}, input, { sighashType: sig.hashType }),
|
Object.assign({}, input, { sighashType: sig.hashType }),
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
)
|
)
|
||||||
: { hash: hashCache, script: scriptCache };
|
: { hash: hashCache, script: scriptCache };
|
||||||
|
@ -391,7 +368,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
this.inputs,
|
this.inputs,
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
);
|
);
|
||||||
const partialSig = {
|
const partialSig = {
|
||||||
|
@ -408,7 +385,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
this.inputs,
|
this.inputs,
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
);
|
);
|
||||||
Promise.resolve(keyPair.sign(hash)).then(signature => {
|
Promise.resolve(keyPair.sign(hash)).then(signature => {
|
||||||
|
@ -847,6 +824,36 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
|
||||||
}
|
}
|
||||||
return cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
return cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
||||||
}
|
}
|
||||||
|
function getInputAdder(cache) {
|
||||||
|
const selfCache = cache;
|
||||||
|
return (_inputData, txBuf) => {
|
||||||
|
if (
|
||||||
|
!txBuf ||
|
||||||
|
_inputData.hash === undefined ||
|
||||||
|
_inputData.index === undefined ||
|
||||||
|
(!Buffer.isBuffer(_inputData.hash) &&
|
||||||
|
typeof _inputData.hash !== 'string') ||
|
||||||
|
typeof _inputData.index !== 'number'
|
||||||
|
) {
|
||||||
|
throw new Error('Error adding input.');
|
||||||
|
}
|
||||||
|
const prevHash = Buffer.isBuffer(_inputData.hash)
|
||||||
|
? _inputData.hash
|
||||||
|
: bufferutils_1.reverseBuffer(Buffer.from(_inputData.hash, 'hex'));
|
||||||
|
// Check if input already exists in cache.
|
||||||
|
const input = { hash: prevHash, index: _inputData.index };
|
||||||
|
checkTxInputCache(selfCache, input);
|
||||||
|
selfCache.__TX.ins.push(
|
||||||
|
Object.assign({}, input, {
|
||||||
|
script: Buffer.alloc(0),
|
||||||
|
sequence:
|
||||||
|
_inputData.sequence || transaction_1.Transaction.DEFAULT_SEQUENCE,
|
||||||
|
witness: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return selfCache.__TX.toBuffer();
|
||||||
|
};
|
||||||
|
}
|
||||||
function check32Bit(num) {
|
function check32Bit(num) {
|
||||||
if (
|
if (
|
||||||
typeof num !== 'number' ||
|
typeof num !== 'number' ||
|
||||||
|
|
12
test/psbt.js
12
test/psbt.js
|
@ -397,9 +397,9 @@ describe(`Psbt`, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(psbt.inputCount, 1)
|
assert.strictEqual(psbt.inputCount, 1)
|
||||||
assert.strictEqual(psbt.__TX.ins[0].sequence, 0xffffffff)
|
assert.strictEqual(psbt.__CACHE.__TX.ins[0].sequence, 0xffffffff)
|
||||||
psbt.setSequence(0, 0)
|
psbt.setSequence(0, 0)
|
||||||
assert.strictEqual(psbt.__TX.ins[0].sequence, 0)
|
assert.strictEqual(psbt.__CACHE.__TX.ins[0].sequence, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws if input index is too high', () => {
|
it('throws if input index is too high', () => {
|
||||||
|
@ -467,24 +467,24 @@ describe(`Psbt`, () => {
|
||||||
it('fromTransaction returns Psbt type (not base class)', () => {
|
it('fromTransaction returns Psbt type (not base class)', () => {
|
||||||
const psbt = Psbt.fromTransaction(Buffer.from([2,0,0,0,0,0,0,0,0,0]));
|
const psbt = Psbt.fromTransaction(Buffer.from([2,0,0,0,0,0,0,0,0,0]));
|
||||||
assert.strictEqual(psbt instanceof Psbt, true);
|
assert.strictEqual(psbt instanceof Psbt, true);
|
||||||
assert.ok(psbt.__TX);
|
assert.ok(psbt.__CACHE.__TX);
|
||||||
})
|
})
|
||||||
it('fromBuffer returns Psbt type (not base class)', () => {
|
it('fromBuffer returns Psbt type (not base class)', () => {
|
||||||
const psbt = Psbt.fromBuffer(Buffer.from(
|
const psbt = Psbt.fromBuffer(Buffer.from(
|
||||||
'70736274ff01000a01000000000000000000000000', 'hex' //cHNidP8BAAoBAAAAAAAAAAAAAAAA
|
'70736274ff01000a01000000000000000000000000', 'hex' //cHNidP8BAAoBAAAAAAAAAAAAAAAA
|
||||||
));
|
));
|
||||||
assert.strictEqual(psbt instanceof Psbt, true);
|
assert.strictEqual(psbt instanceof Psbt, true);
|
||||||
assert.ok(psbt.__TX);
|
assert.ok(psbt.__CACHE.__TX);
|
||||||
})
|
})
|
||||||
it('fromBase64 returns Psbt type (not base class)', () => {
|
it('fromBase64 returns Psbt type (not base class)', () => {
|
||||||
const psbt = Psbt.fromBase64('cHNidP8BAAoBAAAAAAAAAAAAAAAA');
|
const psbt = Psbt.fromBase64('cHNidP8BAAoBAAAAAAAAAAAAAAAA');
|
||||||
assert.strictEqual(psbt instanceof Psbt, true);
|
assert.strictEqual(psbt instanceof Psbt, true);
|
||||||
assert.ok(psbt.__TX);
|
assert.ok(psbt.__CACHE.__TX);
|
||||||
})
|
})
|
||||||
it('fromHex returns Psbt type (not base class)', () => {
|
it('fromHex returns Psbt type (not base class)', () => {
|
||||||
const psbt = Psbt.fromHex('70736274ff01000a01000000000000000000000000');
|
const psbt = Psbt.fromHex('70736274ff01000a01000000000000000000000000');
|
||||||
assert.strictEqual(psbt instanceof Psbt, true);
|
assert.strictEqual(psbt instanceof Psbt, true);
|
||||||
assert.ok(psbt.__TX);
|
assert.ok(psbt.__CACHE.__TX);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
169
ts_src/psbt.ts
169
ts_src/psbt.ts
|
@ -34,7 +34,7 @@ export class Psbt extends PsbtBase {
|
||||||
const tx = Transaction.fromBuffer(txBuf);
|
const tx = Transaction.fromBuffer(txBuf);
|
||||||
checkTxEmpty(tx);
|
checkTxEmpty(tx);
|
||||||
const psbt = new this() as Psbt;
|
const psbt = new this() as Psbt;
|
||||||
psbt.__TX = tx;
|
psbt.__CACHE.__TX = tx;
|
||||||
checkTxForDupeIns(tx, psbt.__CACHE);
|
checkTxForDupeIns(tx, psbt.__CACHE);
|
||||||
let inputCount = tx.ins.length;
|
let inputCount = tx.ins.length;
|
||||||
let outputCount = tx.outs.length;
|
let outputCount = tx.outs.length;
|
||||||
|
@ -71,25 +71,22 @@ export class Psbt extends PsbtBase {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const psbt = super.fromBuffer(buffer, txCountGetter) as Psbt;
|
const psbt = super.fromBuffer(buffer, txCountGetter) as Psbt;
|
||||||
psbt.__TX = tx!;
|
psbt.__CACHE.__TX = tx!;
|
||||||
checkTxForDupeIns(tx!, psbt.__CACHE);
|
checkTxForDupeIns(tx!, psbt.__CACHE);
|
||||||
return psbt as InstanceType<T>;
|
return psbt as InstanceType<T>;
|
||||||
}
|
}
|
||||||
private __CACHE = {
|
private __CACHE: PsbtCache = {
|
||||||
__NON_WITNESS_UTXO_TX_CACHE: [] as Transaction[],
|
__NON_WITNESS_UTXO_TX_CACHE: [],
|
||||||
__NON_WITNESS_UTXO_BUF_CACHE: [] as Buffer[],
|
__NON_WITNESS_UTXO_BUF_CACHE: [],
|
||||||
__TX_IN_CACHE: {} as { [index: string]: number },
|
__TX_IN_CACHE: {},
|
||||||
|
__TX: new Transaction(),
|
||||||
};
|
};
|
||||||
private __TX: Transaction;
|
|
||||||
private __TX_BUF_CACHE?: Buffer;
|
|
||||||
private __FEE_RATE?: number;
|
|
||||||
private __EXTRACTED_TX?: Transaction;
|
|
||||||
private opts: PsbtOpts;
|
private opts: PsbtOpts;
|
||||||
constructor(opts: PsbtOptsOptional = {}) {
|
constructor(opts: PsbtOptsOptional = {}) {
|
||||||
super();
|
super();
|
||||||
// set defaults
|
// set defaults
|
||||||
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
||||||
this.__TX = Transaction.fromBuffer(this.globalMap.unsignedTx!);
|
this.__CACHE.__TX = Transaction.fromBuffer(this.globalMap.unsignedTx!);
|
||||||
this.setVersion(2);
|
this.setVersion(2);
|
||||||
|
|
||||||
// set cache
|
// set cache
|
||||||
|
@ -98,15 +95,15 @@ export class Psbt extends PsbtBase {
|
||||||
Object.defineProperty(this.globalMap, 'unsignedTx', {
|
Object.defineProperty(this.globalMap, 'unsignedTx', {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get(): Buffer {
|
get(): Buffer {
|
||||||
if (self.__TX_BUF_CACHE !== undefined) {
|
if (self.__CACHE.__TX_BUF_CACHE !== undefined) {
|
||||||
return self.__TX_BUF_CACHE;
|
return self.__CACHE.__TX_BUF_CACHE;
|
||||||
} else {
|
} else {
|
||||||
self.__TX_BUF_CACHE = self.__TX.toBuffer();
|
self.__CACHE.__TX_BUF_CACHE = self.__CACHE.__TX.toBuffer();
|
||||||
return self.__TX_BUF_CACHE;
|
return self.__CACHE.__TX_BUF_CACHE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set(data: Buffer): void {
|
set(data: Buffer): void {
|
||||||
self.__TX_BUF_CACHE = data;
|
self.__CACHE.__TX_BUF_CACHE = data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -123,8 +120,6 @@ export class Psbt extends PsbtBase {
|
||||||
});
|
});
|
||||||
dpew(this, '__TX', false, true);
|
dpew(this, '__TX', false, true);
|
||||||
dpew(this, '__EXTRACTED_TX', false, true);
|
dpew(this, '__EXTRACTED_TX', false, true);
|
||||||
dpew(this, '__FEE_RATE', false, true);
|
|
||||||
dpew(this, '__TX_BUF_CACHE', false, true);
|
|
||||||
dpew(this, '__CACHE', false, true);
|
dpew(this, '__CACHE', false, true);
|
||||||
dpew(this, 'opts', false, true);
|
dpew(this, 'opts', false, true);
|
||||||
}
|
}
|
||||||
|
@ -141,69 +136,42 @@ export class Psbt extends PsbtBase {
|
||||||
setVersion(version: number): this {
|
setVersion(version: number): this {
|
||||||
check32Bit(version);
|
check32Bit(version);
|
||||||
checkInputsForPartialSig(this.inputs, 'setVersion');
|
checkInputsForPartialSig(this.inputs, 'setVersion');
|
||||||
this.__TX.version = version;
|
const c = this.__CACHE;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
c.__TX.version = version;
|
||||||
this.__EXTRACTED_TX = undefined;
|
c.__TX_BUF_CACHE = undefined;
|
||||||
|
c.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocktime(locktime: number): this {
|
setLocktime(locktime: number): this {
|
||||||
check32Bit(locktime);
|
check32Bit(locktime);
|
||||||
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
||||||
this.__TX.locktime = locktime;
|
const c = this.__CACHE;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
c.__TX.locktime = locktime;
|
||||||
this.__EXTRACTED_TX = undefined;
|
c.__TX_BUF_CACHE = undefined;
|
||||||
|
c.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSequence(inputIndex: number, sequence: number): this {
|
setSequence(inputIndex: number, sequence: number): this {
|
||||||
check32Bit(sequence);
|
check32Bit(sequence);
|
||||||
checkInputsForPartialSig(this.inputs, 'setSequence');
|
checkInputsForPartialSig(this.inputs, 'setSequence');
|
||||||
if (this.__TX.ins.length <= inputIndex) {
|
const c = this.__CACHE;
|
||||||
|
if (c.__TX.ins.length <= inputIndex) {
|
||||||
throw new Error('Input index too high');
|
throw new Error('Input index too high');
|
||||||
}
|
}
|
||||||
this.__TX.ins[inputIndex].sequence = sequence;
|
c.__TX.ins[inputIndex].sequence = sequence;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
c.__TX_BUF_CACHE = undefined;
|
||||||
this.__EXTRACTED_TX = undefined;
|
c.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
addInput(inputData: TransactionInput): this {
|
addInput(inputData: TransactionInput): this {
|
||||||
checkInputsForPartialSig(this.inputs, 'addInput');
|
checkInputsForPartialSig(this.inputs, 'addInput');
|
||||||
const self = this;
|
const inputAdder = getInputAdder(this.__CACHE);
|
||||||
const inputAdder = (
|
|
||||||
_inputData: TransactionInput,
|
|
||||||
txBuf: Buffer,
|
|
||||||
): Buffer => {
|
|
||||||
if (
|
|
||||||
!txBuf ||
|
|
||||||
(_inputData as any).hash === undefined ||
|
|
||||||
(_inputData as any).index === undefined ||
|
|
||||||
(!Buffer.isBuffer((_inputData as any).hash) &&
|
|
||||||
typeof (_inputData as any).hash !== 'string') ||
|
|
||||||
typeof (_inputData as any).index !== 'number'
|
|
||||||
) {
|
|
||||||
throw new Error('Error adding input.');
|
|
||||||
}
|
|
||||||
const prevHash = Buffer.isBuffer(_inputData.hash)
|
|
||||||
? _inputData.hash
|
|
||||||
: reverseBuffer(Buffer.from(_inputData.hash, 'hex'));
|
|
||||||
|
|
||||||
// Check if input already exists in cache.
|
|
||||||
const input = { hash: prevHash, index: _inputData.index };
|
|
||||||
checkTxInputCache(self.__CACHE, input);
|
|
||||||
|
|
||||||
self.__TX.ins.push({
|
|
||||||
...input,
|
|
||||||
script: Buffer.alloc(0),
|
|
||||||
sequence: _inputData.sequence || Transaction.DEFAULT_SEQUENCE,
|
|
||||||
witness: [],
|
|
||||||
});
|
|
||||||
return self.__TX.toBuffer();
|
|
||||||
};
|
|
||||||
super.addInput(inputData, inputAdder);
|
super.addInput(inputData, inputAdder);
|
||||||
this.__FEE_RATE = undefined;
|
this.__CACHE.__FEE_RATE = undefined;
|
||||||
this.__EXTRACTED_TX = undefined;
|
this.__CACHE.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,15 +197,15 @@ export class Psbt extends PsbtBase {
|
||||||
) {
|
) {
|
||||||
throw new Error('Error adding output.');
|
throw new Error('Error adding output.');
|
||||||
}
|
}
|
||||||
self.__TX.outs.push({
|
self.__CACHE.__TX.outs.push({
|
||||||
script: (_outputData as any).script!,
|
script: (_outputData as any).script!,
|
||||||
value: _outputData.value,
|
value: _outputData.value,
|
||||||
});
|
});
|
||||||
return self.__TX.toBuffer();
|
return self.__CACHE.__TX.toBuffer();
|
||||||
};
|
};
|
||||||
super.addOutput(outputData, true, outputAdder);
|
super.addOutput(outputData, true, outputAdder);
|
||||||
this.__FEE_RATE = undefined;
|
this.__CACHE.__FEE_RATE = undefined;
|
||||||
this.__EXTRACTED_TX = undefined;
|
this.__CACHE.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,8 +222,8 @@ export class Psbt extends PsbtBase {
|
||||||
extractTransaction(disableFeeCheck?: boolean): Transaction {
|
extractTransaction(disableFeeCheck?: boolean): Transaction {
|
||||||
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
||||||
if (!disableFeeCheck) {
|
if (!disableFeeCheck) {
|
||||||
const feeRate = this.__FEE_RATE || this.getFeeRate();
|
const feeRate = this.__CACHE.__FEE_RATE || this.getFeeRate();
|
||||||
const vsize = this.__EXTRACTED_TX!.virtualSize();
|
const vsize = this.__CACHE.__EXTRACTED_TX!.virtualSize();
|
||||||
const satoshis = feeRate * vsize;
|
const satoshis = feeRate * vsize;
|
||||||
if (feeRate >= this.opts.maximumFeeRate) {
|
if (feeRate >= this.opts.maximumFeeRate) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -267,8 +235,8 @@ export class Psbt extends PsbtBase {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.__EXTRACTED_TX) return this.__EXTRACTED_TX;
|
if (this.__CACHE.__EXTRACTED_TX) return this.__CACHE.__EXTRACTED_TX;
|
||||||
const tx = this.__TX.clone();
|
const tx = this.__CACHE.__TX.clone();
|
||||||
this.inputs.forEach((input, idx) => {
|
this.inputs.forEach((input, idx) => {
|
||||||
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
||||||
if (input.finalScriptWitness) {
|
if (input.finalScriptWitness) {
|
||||||
|
@ -277,22 +245,22 @@ export class Psbt extends PsbtBase {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.__EXTRACTED_TX = tx;
|
this.__CACHE.__EXTRACTED_TX = tx;
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeeRate(): number {
|
getFeeRate(): number {
|
||||||
if (!this.inputs.every(isFinalized))
|
if (!this.inputs.every(isFinalized))
|
||||||
throw new Error('PSBT must be finalized to calculate fee rate');
|
throw new Error('PSBT must be finalized to calculate fee rate');
|
||||||
if (this.__FEE_RATE) return this.__FEE_RATE;
|
if (this.__CACHE.__FEE_RATE) return this.__CACHE.__FEE_RATE;
|
||||||
let tx: Transaction;
|
let tx: Transaction;
|
||||||
let inputAmount = 0;
|
let inputAmount = 0;
|
||||||
let mustFinalize = true;
|
let mustFinalize = true;
|
||||||
if (this.__EXTRACTED_TX) {
|
if (this.__CACHE.__EXTRACTED_TX) {
|
||||||
tx = this.__EXTRACTED_TX;
|
tx = this.__CACHE.__EXTRACTED_TX;
|
||||||
mustFinalize = false;
|
mustFinalize = false;
|
||||||
} else {
|
} else {
|
||||||
tx = this.__TX.clone();
|
tx = this.__CACHE.__TX.clone();
|
||||||
}
|
}
|
||||||
this.inputs.forEach((input, idx) => {
|
this.inputs.forEach((input, idx) => {
|
||||||
if (mustFinalize && input.finalScriptSig)
|
if (mustFinalize && input.finalScriptSig)
|
||||||
|
@ -306,20 +274,20 @@ export class Psbt extends PsbtBase {
|
||||||
inputAmount += input.witnessUtxo.value;
|
inputAmount += input.witnessUtxo.value;
|
||||||
} else if (input.nonWitnessUtxo) {
|
} else if (input.nonWitnessUtxo) {
|
||||||
const nwTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, idx);
|
const nwTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, idx);
|
||||||
const vout = this.__TX.ins[idx].index;
|
const vout = this.__CACHE.__TX.ins[idx].index;
|
||||||
const out = nwTx.outs[vout] as Output;
|
const out = nwTx.outs[vout] as Output;
|
||||||
inputAmount += out.value;
|
inputAmount += out.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.__EXTRACTED_TX = tx;
|
this.__CACHE.__EXTRACTED_TX = tx;
|
||||||
const outputAmount = (tx.outs as Output[]).reduce(
|
const outputAmount = (tx.outs as Output[]).reduce(
|
||||||
(total, o) => total + o.value,
|
(total, o) => total + o.value,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const fee = inputAmount - outputAmount;
|
const fee = inputAmount - outputAmount;
|
||||||
const bytes = tx.virtualSize();
|
const bytes = tx.virtualSize();
|
||||||
this.__FEE_RATE = Math.floor(fee / bytes);
|
this.__CACHE.__FEE_RATE = Math.floor(fee / bytes);
|
||||||
return this.__FEE_RATE;
|
return this.__CACHE.__FEE_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizeAllInputs(): {
|
finalizeAllInputs(): {
|
||||||
|
@ -341,7 +309,7 @@ export class Psbt extends PsbtBase {
|
||||||
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
);
|
);
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
|
@ -388,7 +356,7 @@ export class Psbt extends PsbtBase {
|
||||||
? getHashForSig(
|
? getHashForSig(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
Object.assign({}, input, { sighashType: sig.hashType }),
|
Object.assign({}, input, { sighashType: sig.hashType }),
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
)
|
)
|
||||||
: { hash: hashCache!, script: scriptCache! };
|
: { hash: hashCache!, script: scriptCache! };
|
||||||
|
@ -464,7 +432,7 @@ export class Psbt extends PsbtBase {
|
||||||
this.inputs,
|
this.inputs,
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -485,7 +453,7 @@ export class Psbt extends PsbtBase {
|
||||||
this.inputs,
|
this.inputs,
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__CACHE.__TX,
|
||||||
this.__CACHE,
|
this.__CACHE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -517,6 +485,10 @@ interface PsbtCache {
|
||||||
__NON_WITNESS_UTXO_TX_CACHE: Transaction[];
|
__NON_WITNESS_UTXO_TX_CACHE: Transaction[];
|
||||||
__NON_WITNESS_UTXO_BUF_CACHE: Buffer[];
|
__NON_WITNESS_UTXO_BUF_CACHE: Buffer[];
|
||||||
__TX_IN_CACHE: { [index: string]: number };
|
__TX_IN_CACHE: { [index: string]: number };
|
||||||
|
__TX: Transaction;
|
||||||
|
__TX_BUF_CACHE?: Buffer;
|
||||||
|
__FEE_RATE?: number;
|
||||||
|
__EXTRACTED_TX?: Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PsbtOptsOptional {
|
interface PsbtOptsOptional {
|
||||||
|
@ -1065,6 +1037,39 @@ function nonWitnessUtxoTxFromCache(
|
||||||
return cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
return cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInputAdder(
|
||||||
|
cache: PsbtCache,
|
||||||
|
): (_inputData: TransactionInput, txBuf: Buffer) => Buffer {
|
||||||
|
const selfCache = cache;
|
||||||
|
return (_inputData: TransactionInput, txBuf: Buffer): Buffer => {
|
||||||
|
if (
|
||||||
|
!txBuf ||
|
||||||
|
(_inputData as any).hash === undefined ||
|
||||||
|
(_inputData as any).index === undefined ||
|
||||||
|
(!Buffer.isBuffer((_inputData as any).hash) &&
|
||||||
|
typeof (_inputData as any).hash !== 'string') ||
|
||||||
|
typeof (_inputData as any).index !== 'number'
|
||||||
|
) {
|
||||||
|
throw new Error('Error adding input.');
|
||||||
|
}
|
||||||
|
const prevHash = Buffer.isBuffer(_inputData.hash)
|
||||||
|
? _inputData.hash
|
||||||
|
: reverseBuffer(Buffer.from(_inputData.hash, 'hex'));
|
||||||
|
|
||||||
|
// Check if input already exists in cache.
|
||||||
|
const input = { hash: prevHash, index: _inputData.index };
|
||||||
|
checkTxInputCache(selfCache, input);
|
||||||
|
|
||||||
|
selfCache.__TX.ins.push({
|
||||||
|
...input,
|
||||||
|
script: Buffer.alloc(0),
|
||||||
|
sequence: _inputData.sequence || Transaction.DEFAULT_SEQUENCE,
|
||||||
|
witness: [],
|
||||||
|
});
|
||||||
|
return selfCache.__TX.toBuffer();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function check32Bit(num: number): void {
|
function check32Bit(num: number): void {
|
||||||
if (
|
if (
|
||||||
typeof num !== 'number' ||
|
typeof num !== 'number' ||
|
||||||
|
|
4
types/psbt.d.ts
vendored
4
types/psbt.d.ts
vendored
|
@ -8,10 +8,6 @@ export declare class Psbt extends PsbtBase {
|
||||||
static fromTransaction<T extends typeof PsbtBase>(this: T, txBuf: Buffer): InstanceType<T>;
|
static fromTransaction<T extends typeof PsbtBase>(this: T, txBuf: Buffer): InstanceType<T>;
|
||||||
static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>;
|
static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>;
|
||||||
private __CACHE;
|
private __CACHE;
|
||||||
private __TX;
|
|
||||||
private __TX_BUF_CACHE?;
|
|
||||||
private __FEE_RATE?;
|
|
||||||
private __EXTRACTED_TX?;
|
|
||||||
private opts;
|
private opts;
|
||||||
constructor(opts?: PsbtOptsOptional);
|
constructor(opts?: PsbtOptsOptional);
|
||||||
readonly inputCount: number;
|
readonly inputCount: number;
|
||||||
|
|
Loading…
Add table
Reference in a new issue