Skip to content

Conversation

moisesPompilio
Copy link
Contributor

Pending payments that are invalidated or removed from the mempool are now marked as failed. Currently, “failed” simply means the transaction is no longer in BDK’s canonical transaction list while still pending in our DB. It could later return to a pending or confirmed state if it reappears. More precise context for these “failed” states will be possible once bdk_wallet#257 is merged, as that adds richer uncanonica transaction status details.

Additional improvements:

  • Property-based tests simulating multiple RBF attempts, random confirmations, and chain reorgs to ensure correct handling when any RBF transaction confirms.
  • generate_block_and_insert_transactions to mine blocks with chosen transactions, enabling tests where either the original or a replacement transaction is confirmed.
  • Refactored node setup in tests with a new_node helper for clarity.

Note: This PR partially addresses issue #452. I believe it does not conflict with PR #628, as this change is more general. It handles any transaction that is no longer canonical in BDK, not just RBF replacements. This fixes the problem where such transactions would remain pending indefinitely due to lack of further updates, by marking them as failed. PR #628, on the other hand, specifically handles RBFs initiated by the node.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Sep 23, 2025

I've assigned @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

} else {
PaymentStatus::Pending
};
if payment_status == PaymentStatus::Pending {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. Since PaymentStatus::Pending is only assigned in the else branch above, we can move the unconfirmed_txid.push(id) call directly into that block. This avoids the extra comparison afterward and makes the logic a bit more concise and self-contained.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks

@moisesPompilio moisesPompilio force-pushed the fix-rbf-acumulate-transaction-electrum-esplora branch from 56a50c1 to 569cebe Compare September 26, 2025 21:44
@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

…testing

- Introduce `generate_block_and_insert_transactions` to manually mine a block with arbitrary transactions.
- Update `bump_fee_and_broadcast` to optionally insert transactions directly into a block (bypassing the mempool) when `is_insert_block` is true.
- Add integration test to insert the original (pre-RBF) transaction into a block instead of the RBF, to cover scenarios where the original transaction is confirmed and the RBF is not.
This test simulates multiple RBF transactions, randomly confirms one, then simulates a reorg and confirms another at random. This ensures the wallet correctly handles cases where any RBF transaction may be confirmed after
…oval

Pending payments that become invalid or are removed from the mempool are now marked as failed. RBF tests were updated to check that only confirmed transactions succeed and all others are failed.only the actually confirmed transactions have the correct
@moisesPompilio moisesPompilio force-pushed the fix-rbf-acumulate-transaction-electrum-esplora branch from 569cebe to c635cb4 Compare October 7, 2025 19:38
@ldk-reviews-bot
Copy link

🔔 6th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excuse the delay here!

(node_a, node_b)
}

pub(crate) fn new_node(chain_source: &TestChainSource, anchor_channels: bool) -> TestNode {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from the config_node macro to this method makes sense to me, but maybe we want to align names here?

Should this rather be renamed to setup_node, while renaming the current setup_node to setup_node_from_config?

use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use bitcoin::hashes::{hex::FromHex, Hash};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please group imports at the module level.

let address = bitcoind.new_address().expect("failed to get new address");

let request_block_template =
corepc_node::TemplateRequest { rules: vec![electrsd::corepc_node::TemplateRules::Segwit] };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please avoid the full type qualifiers, rather import the types via use above.

node
}

pub(crate) fn generate_block_and_insert_transactions<E: ElectrumApi>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would be simpler to

  1. Put the RBF into the mempool
  2. Sync the wallet so it registers
  3. Mine a block
  4. Call Bitcoin Core's invalidateblock to discard the tip and the RBF
  5. Put the original TX into the mempool
  6. Sync the wallet again

If I'm not mistaken that would save us all of this boilerplate?

let id = failed_payment.id;
let payment_update = PaymentDetailsUpdate {
id,
status: Some(PaymentStatus::Failed),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not convinced we should do this, as on a semantic level there is no such thing as a 'failed' onchain payment. Valid transactions are forever pending as they could always be rebroadcasted even after being evicted from the local mempool. That is why we currently only track them as Pending or Confirmed (once they are irrevocably confirmed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants