Skip to content

Commit

Permalink
Add RBF parameter support to PSBT inputs, set RBF default to true
Browse files Browse the repository at this point in the history
- Updated README.md to include `rbf` parameter in the `updatePsbtAsInput` method.
- Modified `src/descriptors.ts` to handle `rbf` parameter for PSBT inputs.
- Updated `src/psbt.ts` to integrate RBF logic with nSequence.
- Set RBF parameter default to true, altering default behavior from previous versions.

This update ensures transactions using relative timelocks correctly opt into RBF, providing flexibility for transaction fee adjustments while ensuring compatibility with relative timelocks.
  • Loading branch information
landabaso committed Jul 4, 2024
1 parent 0511e64 commit ad9a7fd
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 10 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ To call `updatePsbtAsInput()`, use the following syntax:
```javascript
import { Psbt } from 'bitcoinjs-lib';
const psbt = new Psbt();
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout, rbf });
```

Here, `psbt` refers to an instance of the [bitcoinjs-lib Psbt class](https://github.com/bitcoinjs/bitcoinjs-lib). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction.
Here, `psbt` refers to an instance of the [bitcoinjs-lib Psbt class](https://github.com/bitcoinjs/bitcoinjs-lib). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction. Finally, `rbf` is an optional parameter (defaulting to `true`) used to indicate whether the transaction uses Replace-By-Fee (RBF). When RBF is enabled, transactions can be replaced while they are in the mempool with others that have higher fees. Note that RBF is enabled for the entire transaction if at least one input signals it. Also, note that transactions using relative time locks inherently opt into RBF due to the `nSequence` range used.

The method returns the `inputFinalizer()` function. This finalizer function completes a PSBT input by adding the unlocking script (`scriptWitness` or `scriptSig`) that satisfies the previous output's spending conditions. Bear in mind that both `scriptSig` and `scriptWitness` incorporate signatures. As such, you should complete all necessary signing operations before calling `inputFinalizer()`. Detailed [explanations on the `inputFinalizer` method](#signers-and-finalizers-finalize-psbt-input) can be found in the Signers and Finalizers section.

Expand Down
31 changes: 25 additions & 6 deletions src/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
txId?: string;
value?: number;
vout: number;
rbf?: boolean;
}) {
this.updatePsbtAsInput(params);
return params.psbt.data.inputs.length - 1;
Expand All @@ -1195,6 +1196,14 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
*
* When unsure, always use `txHex`, and skip `txId` and `value` for safety.
*
* Use `rbf` to mark whether this tx can be replaced with another with
* higher fee while being in the mempool. Note that a tx will automatically
* be marked as replacable if a single input requests it.
* Note that any transaction using a relative timelock (nSequence < 0x80000000)
* also falls within the RBF range (nSequence < 0xFFFFFFFE), making it
* inherently replaceable. So don't set `rbf` to false if this is tx uses
* relative time locks.
*
* @returns A finalizer function to be used after signing the `psbt`.
* This function ensures that this input is properly finalized.
* The finalizer has this signature:
Expand All @@ -1207,13 +1216,15 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
txHex,
txId,
value,
vout //vector output index
vout, //vector output index
rbf = true
}: {
psbt: Psbt;
txHex?: string;
txId?: string;
value?: number;
vout: number;
rbf?: boolean;
}) {
if (txHex === undefined) {
console.warn(`Warning: missing txHex may allow fee attacks`);
Expand All @@ -1237,7 +1248,8 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
scriptPubKey: this.getScriptPubKey(),
isSegwit,
witnessScript: this.getWitnessScript(),
redeemScript: this.getRedeemScript()
redeemScript: this.getRedeemScript(),
rbf
});
const finalizer = ({
psbt,
Expand Down Expand Up @@ -1283,16 +1295,23 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
scriptPubKey = out.script;
}
const locktime = this.getLockTime() || 0;
let sequence = this.getSequence();
if (sequence === undefined && locktime !== 0) sequence = 0xfffffffe;
if (sequence === undefined && locktime === 0) sequence = 0xffffffff;
const sequence = this.getSequence();
//We don't know whether the user opted for RBF or not. So check that
//at least one of the 2 sequences matches.
const sequenceNoRBF =
sequence !== undefined
? sequence
: locktime === 0
? 0xffffffff
: 0xfffffffe;
const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
const eqBuffers = (buf1: Buffer | undefined, buf2: Buffer | undefined) =>
buf1 instanceof Buffer && buf2 instanceof Buffer
? Buffer.compare(buf1, buf2) === 0
: buf1 === buf2;
if (
Buffer.compare(scriptPubKey, this.getScriptPubKey()) !== 0 ||
sequence !== inputSequence ||
(sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
locktime !== psbt.locktime ||
!eqBuffers(this.getWitnessScript(), input.witnessScript) ||
!eqBuffers(this.getRedeemScript(), input.redeemScript)
Expand Down
14 changes: 12 additions & 2 deletions src/psbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ export function updatePsbt({
scriptPubKey,
isSegwit,
witnessScript,
redeemScript
redeemScript,
rbf
}: {
psbt: Psbt;
vout: number;
Expand All @@ -154,8 +155,11 @@ export function updatePsbt({
isSegwit: boolean;
witnessScript: Buffer | undefined;
redeemScript: Buffer | undefined;
rbf: boolean;
}): number {
//Some data-sanity checks:
if (sequence !== undefined && rbf && sequence > 0xfffffffd)
throw new Error(`Error: incompatible sequence and rbf settings`);
if (!isSegwit && txHex === undefined)
throw new Error(`Error: txHex is mandatory for Non-Segwit inputs`);
if (
Expand Down Expand Up @@ -209,13 +213,19 @@ export function updatePsbt({
// this input's sequence < 0xffffffff
if (sequence === undefined) {
//NOTE: if sequence is undefined, bitcoinjs-lib uses 0xffffffff as default
sequence = 0xfffffffe;
sequence = rbf ? 0xfffffffd : 0xfffffffe;
} else if (sequence > 0xfffffffe) {
throw new Error(
`Error: incompatible sequence: ${sequence} and locktime: ${locktime}`
);
}
if (sequence === undefined && rbf) sequence = 0xfffffffd;
psbt.setLocktime(locktime);
} else {
if (sequence === undefined) {
if (rbf) sequence = 0xfffffffd;
else sequence = 0xffffffff;
}
}

const input: PsbtInputExtended = {
Expand Down

0 comments on commit ad9a7fd

Please sign in to comment.