-
Notifications
You must be signed in to change notification settings - Fork 29
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
base: main
Are you sure you want to change the base?
Fee bumping Package #114
Changes from 1 commit
5c233db
592a875
4f0755c
b00eaae
9b0c967
b7b2fa5
63c129c
4fce4fe
77f0cbe
77f4db4
4f4f97a
07d5a84
d71a0ec
ee553e7
9237196
63969ab
0047714
28a1ae2
8ca1b75
09cceac
47e598f
3c9f3ff
93f5e39
b247da4
6662b2d
f6aeea6
345d6fb
ad3c6ee
a215dea
4597dc9
f39e26d
372f919
d19fd1e
272d946
c75270a
09d9bc5
8da4962
0acce5f
65717ec
dc02fb5
73dd323
db3ae16
fbd36a1
82a3c5c
f142a7f
104bc75
e5c98c1
74511cf
6f35442
dcffd5e
788ff6f
c3f56c4
74c643b
c570faf
539ac54
bddee6e
ad43a38
e8105cb
7150a01
a44bedc
663df1e
09fba1f
90b3227
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,9 +136,22 @@ export const createCancelRbfTransaction = ( | |
newTxTemplate.addInput(BtcTxInputTemplate.fromUTXO(originalInput)); | ||
|
||
// Step 6: Add more inputs if necessary to meet fee requirements | ||
|
||
// We continue adding inputs until both the fee rate is satisfied and | ||
// the absolute fee meets the minimum RBF requirement | ||
for (const utxo of availableInputs) { | ||
if (newTxTemplate.feeRateSatisfied && newTxTemplate.areFeesPaid()) { | ||
break; // Stop adding inputs once fee requirements are met | ||
if ( | ||
newTxTemplate.feeRateSatisfied && | ||
new BigNumber(newTxTemplate.currentFee).gte( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you still use “areFeesPaid” here, which should cover target fees, and then maybe there can be another method that does “are fees greater than” that you can pass the rbf value to for the comparison. This is obviously a very important part of the logic so just thinking of ways to make it as readable as possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also I had a doubt ... do you think that instead of using
While so const fee = BigNumber.max(
newTxTemplate.targetFeesToPay,
txAnalyzer.fee,
); would be correct code then ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is how we intialise the newTxTemplate // Step 3: Create a new transaction template
const newTxTemplate = new BtcTransactionTemplate({
inputs: [],
outputs: [],
targetFeeRate: Number(txAnalyzer.rbfFeeRate), // Use RBF-specific fee rate from analyzer
dustThreshold,
network,
scriptType,
requiredSigners,
totalSigners,
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. like for this fixture .... For this multisig transaction, we have two inputs and three outputs, each of Scenario: RBF to Cancel TransactionSuppose we use RBF to cancel this transaction and set a target fee rate of 80 sat/vB. In a cancellation, the size of the transaction typically decreases because we have only one output (cancellation address), and in most cases, a single input.
However, even though a fee of 12,720 sats would achieve the desired fee rate of 80 sat/vB, we still end up paying 20,880 sats to satisfy the RBF conditions. now as per current code the here // https://mempool.space/tx/be81b81620702718957d445611066bd596fe1840e219b84f6ea60e0114a7a305
{
case: "Create a valid cancel RBF transaction",
originalTx:
"010000000001022b2be3ac975c385defbda23a1eec30946b708c209981a5c07830fd726b3e25530200000000fdffffffe635a5d14ea5eb63d455948c0894d61ac35f7ba00f260f128b725f25855bd6550400000000fdffffff03400caa3b00000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d886c5e1b000000002200200a618b712d918bb1ba59b737c2a37b40d557374754ef2575ce41d08d5f782df980960d04000000002200202122f4719add322f4d727f48379f8a8ba36a40ec4473fd99a2fdcfd89a16e048040047304402202c0be2d56da6f551b92e657a1f29bc410f244d68063bbc14297bae318d06a32d0220276ddf6da48d0dfdc717974aee32b94a45867393a663df8bc1e8382e1f7d9989014730440220649a73751ad934c9e517022bc8b510d53474772286e5678227ae71837ec0307d022031ec829069c7ab474759741c6acd1efb7e0918d5d63e4fbd1071ce3ea1bfc6ec01695221022dfa322241a4946b9ead36ab9c8c55bd4c4340a1290b5bf71d23a695aeb1240a21034d82610a17c332852205e063c64fee21a77fabc7ac0e6d7ada2a820922c9a5dc2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae040047304402207361009ec2357c8d9f178a79772715d29308f1c265bf3c31f083d9253dcedd3c02203fbe90a73f2ca763233cacd9f6be1ebc24abdeff928d5ad454c6fd50b9bc869d014730440220416d0e1acfd95024dc31bc534fee0662ffd906dba98040ac4ed039bf4451176d02203b9416a93b4a116f4edcbfd256e5f232bc9b9f12f6270e84e23597ce68f24ca201695221022dfa322241a4946b9ead36ab9c8c55bd4c4340a1290b5bf71d23a695aeb1240a21034d82610a17c332852205e063c64fee21a77fabc7ac0e6d7ada2a820922c9a5dc2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000",
network: Network.MAINNET,
inputs: [
{
// https://mempool.space/tx/53253e6b72fd3078c0a58199208c706b9430ec1e3aa2bdef5d385c97ace32b2b#flow=&vout=2
txid: "53253e6b72fd3078c0a58199208c706b9430ec1e3aa2bdef5d385c97ace32b2b",
vout: 2,
value: "932049200",
sequence: 4294967293,
},
{
// https://mempool.space/tx/55d65b85255f728b120f260fa07b5fc31ad694088c9455d463eba54ed1a535e6#flow=&vout=4
txid: "55d65b85255f728b120f260fa07b5fc31ad694088c9455d463eba54ed1a535e6",
vout: 4,
value: "596144040",
sequence: 4294967293,
},
],
availableUtxos: [
// (Original Input) https://mempool.space/tx/53253e6b72fd3078c0a58199208c706b9430ec1e3aa2bdef5d385c97ace32b2b#flow=&vout=2
{
txid: "53253e6b72fd3078c0a58199208c706b9430ec1e3aa2bdef5d385c97ace32b2b",
vout: 2,
value: "932049200",
witnessUtxo: {
script: Buffer.from(
"00202122f4719add322f4d727f48379f8a8ba36a40ec4473fd99a2fdcfd89a16e048",
"hex",
),
value: 932049200,
},
prevTxHex:
"010000000145a8a366f1e073e4e1a03172b95e7db2f006d710d3585e5d14a137c218bf90ed03000000fc00473044022056da571c657d063d18217cb3d2cdb827d11fdd0fdef9ffc0ef69345628c0da260220092f7fe2b2d2cba7d1f4d53f0fb7db54dd15c6b8aac2f8e66102778aac0a62250147304402200bf2e4c82a2337168b7370deae483c88521367da8415c65ffa24b21ba4b13a7f02207a946b5c0f9f9213be130022d83f2ee91363c08de8cd2460765e020f59a3399a014c695221026fcec918a19aad4c92527f4bad924c7cc8dfdba935418f3ce217c1c839a58b952103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f8802103d1fc482d299248b97e38d9042a068eb102818a3556d5247d080849a0880a9e2653aefdffffff03400caa3b000000002200203eb5062a0b0850b23a599425289a091c374ca934101d03144f060c5b46a979be181d1a1a000000002200200a618b712d918bb1ba59b737c2a37b40d557374754ef2575ce41d08d5f782df930f18d37000000002200202122f4719add322f4d727f48379f8a8ba36a40ec4473fd99a2fdcfd89a16e04800000000",
},
{
// (Original Input 2) https://mempool.space/tx/55d65b85255f728b120f260fa07b5fc31ad694088c9455d463eba54ed1a535e6#flow=&vout=4
txid: "55d65b85255f728b120f260fa07b5fc31ad694088c9455d463eba54ed1a535e6",
vout: 4,
value: "596144040",
witnessUtxo: {
script: Buffer.from(
"00202122f4719add322f4d727f48379f8a8ba36a40ec4473fd99a2fdcfd89a16e048",
"hex",
),
value: 596144040,
},
prevTxHex:
"010000000145a8a366f1e073e4e1a03172b95e7db2f006d710d3585e5d14a137c218bf90ed05000000fc004730440220414f5352d874fad4171326bdc563f2724eadbbe8e3199fbc83f9d6d04dc02176022072ec117a06d340209b44cef533d1a2f21c0ace64f653f454e745add290938e53014730440220367ff913573c50706003423b47648553730d0377c7707e505d1a4f9a08313ab6022073fad7c23abeb4ca1d48075bd276c7a5aa7b0d942ad741c9a8a4233a264f2df1014c695221026fcec918a19aad4c92527f4bad924c7cc8dfdba935418f3ce217c1c839a58b952103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f8802103d1fc482d299248b97e38d9042a068eb102818a3556d5247d080849a0880a9e2653aefdffffff05400caa3b000000002200200a618b712d918bb1ba59b737c2a37b40d557374754ef2575ce41d08d5f782df90084d71700000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d0084d71700000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d3819b1110000000022002014b288dca5d59caa8868d1668c97c971e58ab3ccf10534ac567ea51aa8aba299a86f8823000000002200202122f4719add322f4d727f48379f8a8ba36a40ec4473fd99a2fdcfd89a16e04800000000",
},
{
// extra UTXO , NOT IN ORIGINAL TX
// https://mempool.space/tx/89caa4dff1251f3a0e62b662ee1b22e8cffeb32bf2cf839ff959d62fe56a3562
txid: "55d65b85255f728b120f260fa07b5fc31ad694088c9455d463eba54ed1a535e6",
vout: 3,
value: "296819000",
witnessUtxo: {
script: Buffer.from(
"002014b288dca5d59caa8868d1668c97c971e58ab3ccf10534ac567ea51aa8aba299",
"hex",
),
value: 296819000,
},
prevTxHex:
"010000000145a8a366f1e073e4e1a03172b95e7db2f006d710d3585e5d14a137c218bf90ed05000000fc004730440220414f5352d874fad4171326bdc563f2724eadbbe8e3199fbc83f9d6d04dc02176022072ec117a06d340209b44cef533d1a2f21c0ace64f653f454e745add290938e53014730440220367ff913573c50706003423b47648553730d0377c7707e505d1a4f9a08313ab6022073fad7c23abeb4ca1d48075bd276c7a5aa7b0d942ad741c9a8a4233a264f2df1014c695221026fcec918a19aad4c92527f4bad924c7cc8dfdba935418f3ce217c1c839a58b952103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f8802103d1fc482d299248b97e38d9042a068eb102818a3556d5247d080849a0880a9e2653aefdffffff05400caa3b000000002200200a618b712d918bb1ba59b737c2a37b40d557374754ef2575ce41d08d5f782df90084d71700000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d0084d71700000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d3819b1110000000022002014b288dca5d59caa8868d1668c97c971e58ab3ccf10534ac567ea51aa8aba299a86f8823000000002200202122f4719add322f4d727f48379f8a8ba36a40ec4473fd99a2fdcfd89a16e04800000000",
},
],
dustThreshold: "546",
targetFeeRate: 80, // original was 60 sat/vB
scriptType: SCRIPT_TYPES.P2WSH,
requiredSigners: 2,
totalSigners: 3,
cancelAddress:
"bc1qyy30guv6m5ez7ntj0ayr08u23w3k5s8vg3elmxdzlh8a3xskupyqn2lp5w", // https://mempool.space/address/bc1qyy30guv6m5ez7ntj0ayr08u23w3k5s8vg3elmxdzlh8a3xskupyqn2lp5w There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh interesting. That’s useful context. Then shouldn’t I’m on mobile so I’m sure I’m missing something from all the different contexts but if the templates target is set from the analyzers rbf recommendation basically, then why doesn’t that catch all of the conditionals you’re looking at? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ahh ... yes actually .... which in this case is as RBF suggests a feeRate of 61 sats/vB There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I am still trying to figure out how can we define this more clearly and remove the complexity ... surrounding it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But I thought the only rate mentioned in the bip was the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, as I've been re-reviewing and thinking about this more, I think part of the complexity comes from this comment you made:
I commented on this in the analyzer as well. This is redirection that can be confusing. It's not clear that first of all that this check is being made which makes Second, I think if you make the change at the top that I suggested, where the targetFeeRate from the user is validated to be at least as much as the original, then you can simplify to this: if (
newTxTemplate.feeRateSatisfied &&
new BigNumber(newTxTemplate.currentFee).gte(txAnalyzer.minimumRbfFee)
) {
// Stop adding inputs when both conditions are met:
// 1. The fee rate satisfies the target rate (which must be gte original tx fee rate
// 2. The current fee is gte the minimum required absolute RBF fee amount
break;
} this should handle the situation you described above where you can hit the target fee rate but still not be paying enough absolute fees (because the replacement tx is smaller) and so you have to keep adding inputs to increase the current fee to be higher than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, one problem here is that you haven't added anything to the change yet (which is why you could run into the issue below about dusty change). So this check should actually confirm that there is enough left over to have more than dust as change. if you add a property to the analyzer if (
newTxTemplate.feeRateSatisfied &&
new BigNumber(newTxTemplate.currentFee).gte(txAnalyzer.minimumRbfFee) &&
newTxTemplate.needsChange
) {
// Stop adding inputs when the following conditions are met:
// 1. The fee rate satisfies the target rate (which must be gte original tx fee rate
// 2. The current fee is gte the minimum required absolute RBF fee amount
// 3. There is enough left over for change
break;
} With this the checks on the change aren't necessary and the extra validation afterwards also probably isn't necessary. Note, this is probably only necessary for cancelation since you need at least one output. For accelerations, you probably already have a change output, or at least have at least one other output and can remove the change if necessary. |
||
BigNumber.max(newTxTemplate.targetFeesToPay, txAnalyzer.minimumRBFFee), | ||
) | ||
) { | ||
// Stop adding inputs when both conditions are met: | ||
// 1. The fee rate satisfies the target rate | ||
// 2. The current fee is greater than or equal to the maximum of: | ||
// a) The target fee based on the desired rate by user | ||
// b) The minimum fee required for a valid RBF replacement | ||
break; | ||
} | ||
// Skip if this UTXO is already added | ||
if (utxo.txid === originalInput.txid && utxo.vout === originalInput.vout) { | ||
|
@@ -148,8 +161,14 @@ export const createCancelRbfTransaction = ( | |
} | ||
|
||
// Step 7: Calculate and set the cancellation output amount | ||
|
||
const totalInputAmount = new BigNumber(newTxTemplate.getTotalInputAmount()); | ||
const fee = new BigNumber(newTxTemplate.targetFeesToPay); | ||
// Ensure we're using the higher of the target fee and the minimum RBF fee | ||
const fee = BigNumber.max( | ||
newTxTemplate.targetFeesToPay, | ||
txAnalyzer.minimumRBFFee, | ||
); | ||
// The cancel output receives all funds minus the fee | ||
const cancelOutputAmount = totalInputAmount.minus(fee); | ||
|
||
if (cancelOutputAmount.isLessThan(dustThreshold)) { | ||
|
@@ -169,8 +188,29 @@ export const createCancelRbfTransaction = ( | |
|
||
// Step 8: Ensure the new transaction meets RBF requirements | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could be its own function as well that's used in both. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lot of this could probably be simplified too. This should throw if:
|
||
const currentFee = new BigNumber(newTxTemplate.currentFee); | ||
const minRequiredFee = new BigNumber(analysis.fee).plus( | ||
txAnalyzer.minimumRBFFee, | ||
const minRequiredFee = new BigNumber(txAnalyzer.minimumRBFFee); | ||
|
||
console.log( | ||
"testing", | ||
"original tx size", | ||
analysis.vsize, | ||
"original tx fees", | ||
analysis.fee, | ||
"original tx feeRate", | ||
analysis.feeRate, | ||
); | ||
|
||
console.log( | ||
"\n modified", | ||
"bumped tx size", | ||
newTxTemplate.estimatedVsize, | ||
"bumped tx fees", | ||
newTxTemplate.currentFee, | ||
"bumped tx targetFeesToPay", | ||
newTxTemplate.targetFeesToPay, | ||
"bumped tx feeRate", | ||
newTxTemplate.estimatedFeeRate, | ||
newTxTemplate, | ||
); | ||
|
||
if (currentFee.isLessThan(minRequiredFee)) { | ||
|
@@ -336,8 +376,18 @@ export const createAcceleratedRbfTransaction = ( | |
|
||
// Step 6: Add more inputs if necessary to meet fee requirements | ||
for (const utxo of availableInputs) { | ||
if (newTxTemplate.feeRateSatisfied && newTxTemplate.areFeesPaid()) { | ||
break; // Stop adding inputs once fee requirements are met | ||
if ( | ||
newTxTemplate.feeRateSatisfied && | ||
new BigNumber(newTxTemplate.currentFee).gte( | ||
BigNumber.max(newTxTemplate.targetFeesToPay, txAnalyzer.minimumRBFFee), | ||
) | ||
) { | ||
// Stop adding inputs when both conditions are met: | ||
// 1. The fee rate satisfies the target rate | ||
// 2. The current fee is greater than or equal to the maximum of: | ||
// a) The target fee based on the desired rate by user | ||
// b) The minimum fee required for a valid RBF replacement | ||
break; | ||
} | ||
// Skip if this UTXO is already added | ||
if ( | ||
|
@@ -356,7 +406,11 @@ export const createAcceleratedRbfTransaction = ( | |
const totalOutputAmount = new BigNumber( | ||
newTxTemplate.getTotalOutputAmount(), | ||
); | ||
const fee = new BigNumber(newTxTemplate.targetFeesToPay); | ||
// Ensure we're using the higher of the target fee and the minimum RBF fee | ||
const fee = BigNumber.max( | ||
newTxTemplate.targetFeesToPay, | ||
txAnalyzer.minimumRBFFee, | ||
); | ||
const changeAmount = totalInputAmount.minus(totalOutputAmount).minus(fee); | ||
|
||
if (changeAmount.gt(dustThreshold)) { | ||
|
@@ -381,9 +435,7 @@ export const createAcceleratedRbfTransaction = ( | |
|
||
// Step 8: Ensure the new transaction meets RBF requirements | ||
const currentFee = new BigNumber(newTxTemplate.currentFee); | ||
const minRequiredFee = new BigNumber(analysis.fee).plus( | ||
txAnalyzer.minimumRBFFee, | ||
); | ||
const minRequiredFee = new BigNumber(txAnalyzer.minimumRBFFee); | ||
|
||
if (currentFee.isLessThan(minRequiredFee)) { | ||
if (strict) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, could still use some work here but I think it's getting close. I think I understand a little better now the requirements on the rbf side and have a suggestion for how to clarify. Basically if the analyzer no longer thinks about rbf rates, and the rbf functions simply pass in the user defined target fee rate and then validates that value is higher than the original tx rate, that should clarify a lot.
I think you also need to make sure to add enough inputs for the cancelation tx so that the change is not dust. That should allow the change code and the validation at the end to be simplified.
Finally, if you try and modularize some more so that duplicated code can be shared, I think that will make things more readable.