Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fee bumping Package #114

Merged
merged 84 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
5c233db
feat: Initialize @caravan/feebumping package
Legend101Zz Jun 23, 2024
592a875
changes in rbf file
Legend101Zz Jun 23, 2024
4f0755c
Add comprehensive fee bumping package for Caravan
Legend101Zz Jul 19, 2024
b00eaae
Implement fee bumping types and estimation functions
Legend101Zz Jul 22, 2024
9b0c967
Add transaction analysis functionality for fee bumping
Legend101Zz Jul 22, 2024
b7b2fa5
Refactor fee bumping package for improved functionality and integration
Legend101Zz Jul 22, 2024
63c129c
Changes to the RBF functionality in the fees package and added RBF si…
Legend101Zz Jul 26, 2024
4fce4fe
Implement CPFP and RBF classes with tests
Legend101Zz Aug 1, 2024
77f0cbe
Refactored RBF and CPFP classes, introduced TransactionAnalyzer class
Legend101Zz Aug 3, 2024
77f4db4
Refactor RBF, CPFP, and TransactionAnalyzer Classes and their Tests
Legend101Zz Aug 6, 2024
4f4f97a
Enhance RBF class functionality with improved state management and fe…
Legend101Zz Aug 14, 2024
07d5a84
Added methods to handle sequence numbers within PSBTs for better RBF …
Legend101Zz Aug 14, 2024
d71a0ec
minor corrections in requiredFee method
Legend101Zz Aug 14, 2024
ee553e7
minor corrections in requiredFee name , changed to requiredAbsoluteFee
Legend101Zz Aug 14, 2024
9237196
Refactor RBF class with improved state management and error handling
Legend101Zz Aug 16, 2024
63969ab
feat: Implement TransactionAnalyzer for raw transaction analysis
Legend101Zz Aug 19, 2024
0047714
Refactor of TransactionAnalyzer classfor Enhanced Fee Calculation and…
Legend101Zz Aug 22, 2024
28a1ae2
Added Bitcoin Transaction Components and Template
Legend101Zz Aug 22, 2024
8ca1b75
feat: Implemented new CPFP/RBF functions
Legend101Zz Aug 22, 2024
09cceac
chore: changes in utils,types and constants files
Legend101Zz Aug 29, 2024
47e598f
refactor: changes in transactionAnalyzer class and it's tests
Legend101Zz Aug 29, 2024
3c9f3ff
refactor : btcTransactionComponents and btcTransactionTemplate classes
Legend101Zz Aug 29, 2024
93f5e39
refactor: Refactored and corrected RBF and CPFP functions
Legend101Zz Aug 29, 2024
b247da4
changes: add util fn to convert btcTemplate to PSBT
Legend101Zz Sep 1, 2024
6662b2d
refactor : btcComponents and btcTemplate ... toPsbt() method , remove…
Legend101Zz Sep 1, 2024
f6aeea6
refactor: transactionAnalyzer ... removed redundant methods
Legend101Zz Sep 1, 2024
345d6fb
refactor: Changes in btcTransactionInput , Add methods for intiatiati…
Legend101Zz Sep 2, 2024
ad3c6ee
refactor : utils , added more util types for PSBT based calculations
Legend101Zz Sep 2, 2024
a215dea
refactor: RBF/CPFP functions to handle new toPSBT() method of templat…
Legend101Zz Sep 2, 2024
4597dc9
tests: added new btcTransactionComponents tests and refactored other …
Legend101Zz Sep 2, 2024
f39e26d
add README
Legend101Zz Sep 2, 2024
372f919
refactor btcTransactionComponents : Changes to inline docstring comme…
Legend101Zz Sep 9, 2024
d19fd1e
minor README changes
Legend101Zz Sep 9, 2024
272d946
refactor btcTransactionComponents : added methods to validate witness…
Legend101Zz Sep 9, 2024
c75270a
corrected and added new tests to btcTransactionComponents
Legend101Zz Sep 9, 2024
09d9bc5
refactored: btcTransactionTemplate class
Legend101Zz Sep 9, 2024
8da4962
Add ScriptType : Added a new script-type extending MULTISIG_ADDRESS_T…
Legend101Zz Sep 9, 2024
0acce5f
refactot transactionAnalyzer
Legend101Zz Sep 9, 2024
65717ec
minor fix: README link
Legend101Zz Sep 11, 2024
dc02fb5
Refactor: Utils file to remove unnecessary functions, move some util …
Legend101Zz Sep 12, 2024
73dd323
refactor: Transaction Analyzer ... correct rbf-fees calculation , ret…
Legend101Zz Sep 12, 2024
db3ae16
minor-refactor: Add detailed doc strings for ABSURDLY_HIGH_ABS_FEE & …
Legend101Zz Sep 12, 2024
fbd36a1
minor btcTransactionComponents changes
Legend101Zz Sep 12, 2024
82a3c5c
refactor: btcTransactionTemplate add addInputToPsbt & addOutputToPsbt…
Legend101Zz Sep 12, 2024
f142a7f
add: isCPFPFeeSatisfied method from utils to cpfp file
Legend101Zz Sep 12, 2024
104bc75
removed reverseTxid fn from utils
Legend101Zz Sep 16, 2024
e5c98c1
correction: Corrected code to extract input txid from input hash
Legend101Zz Sep 16, 2024
74511cf
correction-tests: Transaction Analyzer
Legend101Zz Sep 16, 2024
6f35442
correction-tests: BTC Components
Legend101Zz Sep 16, 2024
dcffd5e
correction tests: BTC Template
Legend101Zz Sep 22, 2024
788ff6f
correction tests: RBF
Legend101Zz Sep 22, 2024
c3f56c4
correction tests: CPFP
Legend101Zz Sep 22, 2024
74c643b
refactor : tsconfig
Legend101Zz Sep 22, 2024
c570faf
correction : RBF function , ensure that the new bumped tx ,pays gte o…
Legend101Zz Sep 24, 2024
539ac54
RBF : remove console.log statements
Legend101Zz Sep 24, 2024
bddee6e
correction : index file , added all exports
Legend101Zz Sep 24, 2024
ad43a38
correction: RBF calculations
Legend101Zz Sep 24, 2024
e8105cb
refactor: RBF fixtures
Legend101Zz Sep 24, 2024
7150a01
minor changes : Transaction Analyzer - RBF FEE CALCULATION COMMENTS .…
Legend101Zz Sep 27, 2024
a44bedc
added comments : types file
Legend101Zz Sep 27, 2024
663df1e
RBF : Add option in RBF tx creation to prevent
Legend101Zz Sep 27, 2024
09fba1f
RBF-Fixtures : minor comment fixes
Legend101Zz Sep 27, 2024
90b3227
changes: README
Legend101Zz Oct 9, 2024
9b4a174
review-changes: btcTransaction Template
Legend101Zz Oct 16, 2024
cb249e2
review-changes: constants.ts
Legend101Zz Oct 16, 2024
ed9f6c7
review-changes: CPFP
Legend101Zz Oct 16, 2024
1109c68
refactor: CPFP
Legend101Zz Oct 16, 2024
e3fe441
minor-refactor: CPFP
Legend101Zz Oct 16, 2024
d0df3dc
correction : validateCPFPPackage
Legend101Zz Oct 17, 2024
22e5479
Fix-BtcTransactionTemplate: adjustChangeOutput, Preserve sole dust ou…
Legend101Zz Oct 17, 2024
69e44cb
Fix-BtcTransactionTemplate : valildate() function , Enhance BtcTransa…
Legend101Zz Oct 17, 2024
d96b3be
Correction-btcTemplate : validate() function
Legend101Zz Oct 17, 2024
fa292dc
Add-method: needsChange
Legend101Zz Oct 17, 2024
ee59d3f
Fix : TxAnalyzer RBF calculations
Legend101Zz Oct 17, 2024
5c80694
Review-changes: RBF
Legend101Zz Oct 17, 2024
c34de8a
Refactor-RBF: Common-functionality between acceleration and cancellat…
Legend101Zz Oct 18, 2024
787be0d
review-changes : PSBTV2
Legend101Zz Oct 18, 2024
4f6b067
Fix-Utils : estimateTransactionVsize
Legend101Zz Oct 18, 2024
945d644
FINAL NITS
Legend101Zz Oct 21, 2024
96b4e44
Merge branch 'main' into fee-bumping
bucko13 Oct 21, 2024
9676c49
refactor-RBF: remove analysis object
Legend101Zz Oct 22, 2024
4057659
review-changes: CPFP
Legend101Zz Oct 22, 2024
d46fa32
FINAL NITS:2
Legend101Zz Oct 23, 2024
8f2335b
FINAL NITS:3
Legend101Zz Oct 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/caravan-fees/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
root: true,
extends: ["@caravan/eslint-config/library.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
};
8 changes: 8 additions & 0 deletions packages/caravan-fees/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
transform: {
"^.+\\.js$": "babel-jest",
"^.+\\.ts$": "ts-jest",
},
};
40 changes: 40 additions & 0 deletions packages/caravan-fees/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@caravan/fees",
"version": "0.0.0-beta",
"description": "Utility library for fee bumping bitcoin transactions",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"module": "./dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "npm run build -- --watch",
"test": "jest src",
"test:watch": "jest --watch src"
},
"keywords": [
"bitcoin",
"cpfp",
"rbf",
"feebumping",
"blockchain"
],
"author": "",
"license": "MIT",
"engines": {
"node": ">=20"
},
"devDependencies": {
"@caravan/eslint-config": "*",
"tsconfig": "*"
},
"dependencies": {
"@caravan/bitcoin": "*"
Legend101Zz marked this conversation as resolved.
Show resolved Hide resolved
}
}
14 changes: 14 additions & 0 deletions packages/caravan-fees/src/constants.ts
Legend101Zz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import BigNumber from "bignumber.js";
Legend101Zz marked this conversation as resolved.
Show resolved Hide resolved

export const MIN_FEE_RATE = 1;
export const MAX_FEE_RATE = 5000;
export const DEFAULT_FEE_INCREASE_THRESHOLD = 1.1;
export const MIN_DUST_AMOUNT = new BigNumber(546);
Legend101Zz marked this conversation as resolved.
Show resolved Hide resolved
export const SATS_PER_BTC = new BigNumber(100000000);
export const DUST_THRESHOLD = 546; // in satoshis
Legend101Zz marked this conversation as resolved.
Show resolved Hide resolved
export const MAX_STANDARD_TX_WEIGHT = 400000; // in weight units
export const MIN_RELAY_FEE_RATE = 1; // in satoshis per byte
export const DEFAULT_DUST_THRESHOLD = 546; // in satoshis
export const RBF_SEQUENCE = 0xffffffff - 2;
export const DEFAULT_MAX_CHILD_TX_SIZE = 1000;
export const DEFAULT_MAX_ADDITIONAL_INPUTS = 3;
37 changes: 37 additions & 0 deletions packages/caravan-fees/src/cpfp.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { PsbtV2 } from "@caravan/psbt";
import { Network } from "@caravan/bitcoin";

const parentPsbtFixture = new PsbtV2();
parentPsbtFixture.addInput({
previousTxId:
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
outputIndex: 0,
witnessUtxo: {
script: Buffer.from("0014000000000000000000000000000000000000", "hex"),
amount: 100000, // 0.001 BTC
},
});
parentPsbtFixture.addOutput({
script: Buffer.from("0014111111111111111111111111111111111111", "hex"),
amount: 90000, // 0.0009 BTC
});

const additionalUtxoFixture = {
txid: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
vout: 0,
value: 50000, // 0.0005 BTC
script: Buffer.from("0014222222222222222222222222222222222222", "hex"),
};

const defaultOptions = {
parentPsbt: parentPsbtFixture,
spendableOutputs: [0],
destinationAddress: "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
feeRate: { satoshisPerByte: 2 },
network: Network.MAINNET,
requiredSigners: 2,
totalSigners: 3,
addressType: "P2WSH",
};

export { parentPsbtFixture, additionalUtxoFixture, defaultOptions };
227 changes: 227 additions & 0 deletions packages/caravan-fees/src/cpfp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { CPFPTransaction, prepareCPFPTransaction } from "./cpfp";
import { PsbtV2 } from "@caravan/psbt";
import { Network } from "@caravan/bitcoin";
import {
parentPsbtFixture,
additionalUtxoFixture,
defaultOptions,
} from "./cpfp.fixtures";

describe("CPFPTransaction", () => {
describe("constructor", () => {
it("should initialize with valid options", () => {
const cpfpTx = new CPFPTransaction(defaultOptions);
expect(cpfpTx).toBeDefined();
});

it("should throw an error if parent PSBT has no inputs", () => {
const invalidPsbt = new PsbtV2();
expect(
() =>
new CPFPTransaction({ ...defaultOptions, parentPsbt: invalidPsbt }),
).toThrow("Parent PSBT has no inputs.");
});

it("should throw an error if no spendable outputs are provided", () => {
expect(
() => new CPFPTransaction({ ...defaultOptions, spendableOutputs: [] }),
).toThrow("No spendable outputs provided.");
});
});

describe("prepareCPFP", () => {
it("should create a valid child transaction", () => {
const cpfpTx = new CPFPTransaction(defaultOptions);
const childPsbt = cpfpTx.prepareCPFP();
expect(childPsbt).toBeInstanceOf(PsbtV2);
expect(childPsbt.PSBT_GLOBAL_INPUT_COUNT).toBe(1);
expect(childPsbt.PSBT_GLOBAL_OUTPUT_COUNT).toBe(1);
});

it("should increase the fee rate", () => {
const cpfpTx = new CPFPTransaction(defaultOptions);
const combinedFee = cpfpTx.getCombinedFee();
const parentFee = cpfpTx.getParentFee();
expect(combinedFee.isGreaterThan(parentFee)).toBe(true);
});

it("should handle high urgency", () => {
const highUrgencyOptions = {
...defaultOptions,
urgency: "high" as const,
};
const cpfpTx = new CPFPTransaction(highUrgencyOptions);
const combinedFee = cpfpTx.getCombinedFee();
const parentFee = cpfpTx.getParentFee();
expect(combinedFee.minus(parentFee).isGreaterThan(parentFee)).toBe(true);
});

it("should add additional inputs when necessary", () => {
const lowValueParentPsbt = new PsbtV2();
lowValueParentPsbt.addInput({
previousTxId:
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
outputIndex: 0,
witnessUtxo: {
script: Buffer.from(
"0014000000000000000000000000000000000000",
"hex",
),
amount: 10000, // 0.0001 BTC
},
});
lowValueParentPsbt.addOutput({
script: Buffer.from("0014111111111111111111111111111111111111", "hex"),
amount: 9000, // 0.00009 BTC
});

const optionsWithAdditionalUtxo = {
...defaultOptions,
parentPsbt: lowValueParentPsbt,
additionalUtxos: [additionalUtxoFixture],
};

const cpfpTx = new CPFPTransaction(optionsWithAdditionalUtxo);
const childPsbt = cpfpTx.prepareCPFP();
expect(childPsbt.PSBT_GLOBAL_INPUT_COUNT).toBe(2);
});

it("should throw an error if resulting output would be dust", () => {
const tinyValueParentPsbt = new PsbtV2();
tinyValueParentPsbt.addInput({
previousTxId:
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
outputIndex: 0,
witnessUtxo: {
script: Buffer.from(
"0014000000000000000000000000000000000000",
"hex",
),
amount: 600, // 600 satoshis
},
});
tinyValueParentPsbt.addOutput({
script: Buffer.from("0014111111111111111111111111111111111111", "hex"),
amount: 550, // 550 satoshis
});

const optionsWithTinyValue = {
...defaultOptions,
parentPsbt: tinyValueParentPsbt,
};

const cpfpTx = new CPFPTransaction(optionsWithTinyValue);
expect(() => cpfpTx.prepareCPFP()).toThrow(
"CPFP transaction would create a dust output",
);
});
});

describe("fee calculations", () => {
it("should correctly calculate child fee", () => {
const cpfpTx = new CPFPTransaction(defaultOptions);
cpfpTx.prepareCPFP();
const childFee = cpfpTx.getChildFee();
expect(childFee.isGreaterThan(0)).toBe(true);
});

it("should correctly calculate parent fee", () => {
const cpfpTx = new CPFPTransaction(defaultOptions);
const parentFee = cpfpTx.getParentFee();
expect(parentFee.toNumber()).toBe(10000); // 0.001 BTC - 0.0009 BTC
});

it("should correctly calculate combined fee", () => {
const cpfpTx = new CPFPTransaction(defaultOptions);
cpfpTx.prepareCPFP();
const combinedFee = cpfpTx.getCombinedFee();
const childFee = cpfpTx.getChildFee();
const parentFee = cpfpTx.getParentFee();
expect(
combinedFee.minus(childFee.plus(parentFee)).abs().isLessThan(1),
).toBe(true);
});
});

describe("prepareCPFPTransaction function", () => {
it("should return a valid PsbtV2 instance", () => {
const childPsbt = prepareCPFPTransaction(defaultOptions);
expect(childPsbt).toBeInstanceOf(PsbtV2);
});

it("should throw an error with invalid options", () => {
const invalidOptions = { ...defaultOptions, parentPsbt: new PsbtV2() };
expect(() => prepareCPFPTransaction(invalidOptions)).toThrow(
"Parent PSBT has no inputs.",
);
});
});

describe("edge cases", () => {
it("should handle maximum number of additional inputs", () => {
const manyAdditionalUtxos = Array(10).fill(additionalUtxoFixture);
const optionsWithManyUtxos = {
...defaultOptions,
maxAdditionalInputs: 5,
additionalUtxos: manyAdditionalUtxos,
};

const cpfpTx = new CPFPTransaction(optionsWithManyUtxos);
const childPsbt = cpfpTx.prepareCPFP();
expect(childPsbt.PSBT_GLOBAL_INPUT_COUNT).toBeLessThanOrEqual(6); // 1 parent output + 5 additional
});

it("should handle custom urgency multipliers", () => {
const customUrgencyOptions = {
...defaultOptions,
urgency: "high" as const,
urgencyMultipliers: { low: 1.1, medium: 1.3, high: 1.8 },
};

const cpfpTx = new CPFPTransaction(customUrgencyOptions);
const combinedFee = cpfpTx.getCombinedFee();
const parentFee = cpfpTx.getParentFee();
const feeIncrease = combinedFee.minus(parentFee);
expect(feeIncrease.dividedBy(parentFee).isGreaterThan(0.7)).toBe(true);
});

it("should respect max child transaction size", () => {
const manyAdditionalUtxos = Array(100).fill(additionalUtxoFixture);
const optionsWithManyUtxos = {
...defaultOptions,
maxChildTxSize: 5,
additionalUtxos: manyAdditionalUtxos,
};

const cpfpTx = new CPFPTransaction(optionsWithManyUtxos);
const childPsbt = cpfpTx.prepareCPFP();
expect(childPsbt.PSBT_GLOBAL_INPUT_COUNT).toBeLessThanOrEqual(5);
});
});

describe("different address types", () => {
const addressTypes = ["P2SH", "P2WSH", "P2SH-P2WSH"];

addressTypes.forEach((addressType) => {
it(`should handle ${addressType} addresses`, () => {
const options = { ...defaultOptions, addressType };
const cpfpTx = new CPFPTransaction(options);
const childPsbt = cpfpTx.prepareCPFP();
expect(childPsbt).toBeInstanceOf(PsbtV2);
});
});
});

describe("different networks", () => {
it("should handle testnet", () => {
const testnetOptions = {
...defaultOptions,
network: Network.TESTNET,
destinationAddress: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx",
};
const cpfpTx = new CPFPTransaction(testnetOptions);
const childPsbt = cpfpTx.prepareCPFP();
expect(childPsbt).toBeInstanceOf(PsbtV2);
});
});
});
Loading