Skip to content

Commit 63dfcf9

Browse files
authored
Merge branch 'bitcoindevkit:master' into maybe-send-async-persister
2 parents 83020a3 + d825f8c commit 63dfcf9

File tree

9 files changed

+130
-90
lines changed

9 files changed

+130
-90
lines changed

.github/workflows/audit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
issues: write
2929
steps:
3030
- name: Checkout
31-
uses: actions/checkout@v5
31+
uses: actions/checkout@v6
3232
with:
3333
persist-credentials: false
3434
- uses: actions-rust-lang/audit@v1

.github/workflows/code_coverage.yml

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
name: Code Coverage
22

3-
# Generates code coverage reports using grcov and uploads results to Coveralls.
3+
# Generates code coverage reports using grcov and uploads results to Codecov.
44
# Runs on every push and pull request to track test coverage metrics.
5-
# Uploads coverage data to Coveralls for tracking and produces an HTML report artifact for download.
5+
# Uploads coverage data to Codecov for tracking and produces an HTML report artifact for download.
66

77
on: [push, pull_request]
88

9-
permissions: {}
9+
permissions:
10+
contents: read
11+
pull-requests: write
1012

1113
jobs:
12-
Codecov:
14+
Coverage:
1315
name: Code Coverage
1416
runs-on: ubuntu-latest
1517
env:
@@ -19,7 +21,7 @@ jobs:
1921

2022
steps:
2123
- name: Checkout
22-
uses: actions/checkout@v5
24+
uses: actions/checkout@v6
2325
with:
2426
persist-credentials: false
2527
- name: Install lcov tools
@@ -40,15 +42,16 @@ jobs:
4042
run: grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --keep-only 'src/**' --ignore 'tests/**' --ignore 'examples/**' -o ./coverage/lcov.info
4143
- name: Generate HTML coverage report
4244
run: genhtml -o coverage-report.html --ignore-errors unmapped ./coverage/lcov.info
43-
- name: Coveralls upload
44-
# Action pinned at tag 2.3.6
45-
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b
45+
- name: Codecov upload
46+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7
4647
with:
47-
github-token: ${{ secrets.GITHUB_TOKEN }}
48-
file: ./coverage/lcov.info
49-
format: lcov
48+
files: ./coverage/lcov.info
49+
flags: rust
50+
name: codecov-bdk-wallet
51+
token: ${{ secrets.CODECOV_TOKEN }}
52+
fail_ci_if_error: false
5053
- name: Upload artifact
51-
uses: actions/upload-artifact@v4
54+
uses: actions/upload-artifact@v5
5255
with:
5356
name: coverage-report
5457
path: coverage-report.html

.github/workflows/cont_integration.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- --all-features
2626
steps:
2727
- name: Checkout
28-
uses: actions/checkout@v5
28+
uses: actions/checkout@v6
2929
with:
3030
persist-credentials: false
3131
# The 'toolchain' argument on this action overrides the Rust compiler version set in rust-toolchain.toml
@@ -55,7 +55,7 @@ jobs:
5555
- --all-features
5656
steps:
5757
- name: Checkout
58-
uses: actions/checkout@v5
58+
uses: actions/checkout@v6
5959
with:
6060
persist-credentials: false
6161
# This action will honor the Rust compiler version set in rust-toolchain.toml. We aim to keep it in sync with
@@ -74,7 +74,7 @@ jobs:
7474
runs-on: ubuntu-latest
7575
steps:
7676
- name: Checkout
77-
uses: actions/checkout@v5
77+
uses: actions/checkout@v6
7878
with:
7979
persist-credentials: false
8080
# This action automatically reads and applies rust-toolchain.toml
@@ -94,7 +94,7 @@ jobs:
9494
CFLAGS: -I/usr/include
9595
steps:
9696
- name: Checkout
97-
uses: actions/checkout@v5
97+
uses: actions/checkout@v6
9898
with:
9999
persist-credentials: false
100100
# Install a recent version of clang that supports wasm32
@@ -117,7 +117,7 @@ jobs:
117117
runs-on: ubuntu-latest
118118
steps:
119119
- name: Checkout
120-
uses: actions/checkout@v5
120+
uses: actions/checkout@v6
121121
with:
122122
persist-credentials: false
123123
# This action automatically reads and applies rust-toolchain.toml
@@ -135,7 +135,7 @@ jobs:
135135
checks: write
136136
steps:
137137
- name: Checkout
138-
uses: actions/checkout@v5
138+
uses: actions/checkout@v6
139139
with:
140140
persist-credentials: false
141141
# This action automatically reads and applies rust-toolchain.toml

.github/workflows/zizmor.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ jobs:
1818
security-events: write
1919
steps:
2020
- name: Checkout
21-
uses: actions/checkout@v5
21+
uses: actions/checkout@v6
2222
with:
2323
persist-credentials: false
2424

2525
- name: Rust Cache
26-
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6
26+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
2727

2828
- name: Install zizmor
2929
run: cargo install zizmor --locked --version 1.6.0
@@ -34,7 +34,7 @@ jobs:
3434
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3535

3636
- name: Upload SARIF file
37-
uses: github/codeql-action/upload-sarif@v3
37+
uses: github/codeql-action/upload-sarif@v4
3838
with:
3939
sarif_file: results.sarif
4040
category: zizmor

.github/zizmor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ rules:
55
policies:
66
# Allow pin by ref/tag
77
actions-rust-lang/setup-rust-toolchain: ref-pin
8+
github/codeql-action/*: ref-pin
89
actions/*: ref-pin

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"]
1818

1919
[dependencies]
2020
bdk_chain = { version = "0.23.1", features = ["miniscript", "serde"], default-features = false }
21-
bitcoin = { version = "0.32.6", features = ["serde", "base64"], default-features = false }
21+
bitcoin = { version = "0.32.7", features = ["serde", "base64"], default-features = false }
2222
miniscript = { version = "12.3.1", features = ["serde"], default-features = false }
2323
rand_core = { version = "0.6.0" }
2424
serde_json = { version = "1" }

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<a href="https://crates.io/crates/bdk_wallet"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk_wallet.svg"/></a>
1212
<a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT or Apache-2.0 Licensed" src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg"/></a>
1313
<a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
14-
<a href="https://coveralls.io/github/bitcoindevkit/bdk?branch=master"><img src="https://coveralls.io/repos/github/bitcoindevkit/bdk/badge.svg?branch=master"/></a>
14+
<a href="https://codecov.io/github/bitcoindevkit/bdk_wallet" ><img src="https://codecov.io/github/bitcoindevkit/bdk_wallet/graph/badge.svg"/></a>
1515
<a href="https://docs.rs/bdk_wallet"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk_wallet-green"/></a>
1616
<a href="https://blog.rust-lang.org/2025/02/20/Rust-1.85.0/"><img alt="Rustc Version 1.85.0+" src="https://img.shields.io/badge/rustc-1.85.0%2B-lightgrey.svg"/></a>
1717
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>

src/wallet/mod.rs

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,15 +1711,15 @@ impl Wallet {
17111711
&mut self,
17121712
txid: Txid,
17131713
) -> Result<TxBuilder<'_, DefaultCoinSelectionAlgorithm>, BuildFeeBumpError> {
1714-
let graph = self.indexed_graph.graph();
1714+
let tx_graph = self.indexed_graph.graph();
17151715
let txout_index = &self.indexed_graph.index;
17161716
let chain_tip = self.chain.tip().block_id();
1717-
let chain_positions = graph
1717+
let chain_positions: HashMap<Txid, ChainPosition<_>> = tx_graph
17181718
.list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default())
17191719
.map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position))
1720-
.collect::<HashMap<Txid, _>>();
1720+
.collect();
17211721

1722-
let mut tx = graph
1722+
let mut tx = tx_graph
17231723
.get_tx(txid)
17241724
.ok_or(BuildFeeBumpError::TransactionNotFound(txid))?
17251725
.as_ref()
@@ -1746,73 +1746,62 @@ impl Wallet {
17461746
let fee = self
17471747
.calculate_fee(&tx)
17481748
.map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
1749-
let fee_rate = self
1750-
.calculate_fee_rate(&tx)
1751-
.map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
1749+
let fee_rate = fee / tx.weight();
17521750

17531751
// Remove the inputs from the tx and process them.
1754-
let utxos = tx
1752+
let utxos: Vec<WeightedUtxo> = tx
17551753
.input
17561754
.drain(..)
17571755
.map(|txin| -> Result<_, BuildFeeBumpError> {
1758-
graph
1759-
// Get previous transaction.
1760-
.get_tx(txin.previous_output.txid)
1761-
.ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))
1762-
// Get chain position.
1763-
.and_then(|prev_tx| {
1756+
let outpoint = txin.previous_output;
1757+
let prev_txout = tx_graph
1758+
.get_txout(outpoint)
1759+
.cloned()
1760+
.ok_or(BuildFeeBumpError::UnknownUtxo(outpoint))?;
1761+
match txout_index.index_of_spk(prev_txout.script_pubkey.clone()) {
1762+
Some(&(keychain, derivation_index)) => {
1763+
let txout = prev_txout;
17641764
let chain_position = chain_positions
1765-
.get(&txin.previous_output.txid)
1765+
.get(&outpoint.txid)
17661766
.cloned()
1767-
.ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?;
1768-
let prev_txout = prev_tx
1769-
.output
1770-
.get(txin.previous_output.vout as usize)
1771-
.ok_or(BuildFeeBumpError::InvalidOutputIndex(txin.previous_output))
1772-
.cloned()?;
1773-
Ok((prev_tx, prev_txout, chain_position))
1774-
})
1775-
.map(|(prev_tx, prev_txout, chain_position)| {
1776-
match txout_index.index_of_spk(prev_txout.script_pubkey.clone()) {
1777-
Some(&(keychain, derivation_index)) => WeightedUtxo {
1778-
satisfaction_weight: self
1779-
.public_descriptor(keychain)
1780-
.max_weight_to_satisfy()
1781-
.unwrap(),
1782-
utxo: Utxo::Local(LocalOutput {
1783-
outpoint: txin.previous_output,
1784-
txout: prev_txout.clone(),
1785-
keychain,
1786-
is_spent: true,
1787-
derivation_index,
1788-
chain_position,
1789-
}),
1790-
},
1791-
None => {
1792-
let satisfaction_weight = Weight::from_wu_usize(
1793-
serialize(&txin.script_sig).len() * 4
1794-
+ serialize(&txin.witness).len(),
1795-
);
1796-
WeightedUtxo {
1797-
utxo: Utxo::Foreign {
1798-
outpoint: txin.previous_output,
1799-
sequence: txin.sequence,
1800-
psbt_input: Box::new(psbt::Input {
1801-
witness_utxo: prev_txout
1802-
.script_pubkey
1803-
.witness_version()
1804-
.map(|_| prev_txout.clone()),
1805-
non_witness_utxo: Some(prev_tx.as_ref().clone()),
1806-
..Default::default()
1807-
}),
1808-
},
1809-
satisfaction_weight,
1810-
}
1811-
}
1812-
}
1813-
})
1767+
.ok_or(BuildFeeBumpError::TransactionNotFound(outpoint.txid))?;
1768+
Ok(WeightedUtxo {
1769+
satisfaction_weight: self
1770+
.public_descriptor(keychain)
1771+
.max_weight_to_satisfy()
1772+
.expect("descriptor should be satisfiable"),
1773+
utxo: Utxo::Local(LocalOutput {
1774+
outpoint,
1775+
txout,
1776+
keychain,
1777+
is_spent: true,
1778+
derivation_index,
1779+
chain_position,
1780+
}),
1781+
})
1782+
}
1783+
None => Ok(WeightedUtxo {
1784+
satisfaction_weight: Weight::from_wu_usize(
1785+
serialize(&txin.script_sig).len() * 4 + serialize(&txin.witness).len(),
1786+
),
1787+
utxo: Utxo::Foreign {
1788+
outpoint,
1789+
sequence: txin.sequence,
1790+
psbt_input: Box::new(psbt::Input {
1791+
witness_utxo: prev_txout
1792+
.script_pubkey
1793+
.witness_version()
1794+
.map(|_| prev_txout),
1795+
non_witness_utxo: tx_graph
1796+
.get_tx(outpoint.txid)
1797+
.map(|tx| tx.as_ref().clone()),
1798+
..Default::default()
1799+
}),
1800+
},
1801+
}),
1802+
}
18141803
})
1815-
.collect::<Result<Vec<WeightedUtxo>, BuildFeeBumpError>>()?;
1804+
.collect::<Result<_, _>>()?;
18161805

18171806
if tx.output.len() > 1 {
18181807
let mut change_index = None;
@@ -1832,7 +1821,6 @@ impl Wallet {
18321821
}
18331822

18341823
let params = TxParams {
1835-
// TODO: figure out what rbf option should be?
18361824
version: Some(tx.version),
18371825
recipients: tx
18381826
.output

tests/build_fee_bump.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use bdk_wallet::psbt::PsbtUtils;
88
use bdk_wallet::test_utils::*;
99
use bdk_wallet::KeychainKind;
1010
use bitcoin::{
11-
absolute, transaction, Address, Amount, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction,
12-
TxOut,
11+
absolute, hashes::Hash, psbt, transaction, Address, Amount, FeeRate, OutPoint, ScriptBuf,
12+
Sequence, Transaction, TxOut, Weight,
1313
};
1414

1515
mod common;
@@ -944,3 +944,51 @@ fn test_legacy_bump_fee_absolute_add_input() {
944944

945945
assert_eq!(fee, Amount::from_sat(6_000));
946946
}
947+
948+
// Test that we can fee-bump a tx containing a foreign (p2a) utxo.
949+
#[test]
950+
fn test_bump_fee_pay_to_anchor_foreign_utxo() {
951+
let (mut wallet, _) = get_funded_wallet_wpkh();
952+
let drain_spk = wallet
953+
.next_unused_address(KeychainKind::External)
954+
.script_pubkey();
955+
956+
let witness_utxo = TxOut {
957+
value: Amount::ONE_SAT,
958+
script_pubkey: bitcoin::ScriptBuf::new_p2a(),
959+
};
960+
// Remember to include this as a "floating" txout in the wallet.
961+
let outpoint = OutPoint::new(Hash::hash(b"prev"), 1);
962+
wallet.insert_txout(outpoint, witness_utxo.clone());
963+
let satisfaction_weight = Weight::from_wu(71);
964+
let psbt_input = psbt::Input {
965+
witness_utxo: Some(witness_utxo),
966+
..Default::default()
967+
};
968+
969+
let mut tx_builder = wallet.build_tx();
970+
tx_builder
971+
.add_foreign_utxo(outpoint, psbt_input, satisfaction_weight)
972+
.unwrap()
973+
.only_witness_utxo()
974+
.fee_rate(FeeRate::from_sat_per_vb_unchecked(2))
975+
.drain_to(drain_spk.clone());
976+
let psbt = tx_builder.finish().unwrap();
977+
let tx = psbt.unsigned_tx.clone();
978+
assert!(tx.input.iter().any(|txin| txin.previous_output == outpoint));
979+
let txid1 = tx.compute_txid();
980+
wallet.apply_unconfirmed_txs([(tx, 123456)]);
981+
982+
// Now build fee bump.
983+
let mut tx_builder = wallet
984+
.build_fee_bump(txid1)
985+
.expect("`build_fee_bump` should succeed");
986+
tx_builder
987+
.set_recipients(vec![])
988+
.drain_to(drain_spk)
989+
.only_witness_utxo()
990+
.fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
991+
let psbt = tx_builder.finish().unwrap();
992+
let tx = &psbt.unsigned_tx;
993+
assert!(tx.input.iter().any(|txin| txin.previous_output == outpoint));
994+
}

0 commit comments

Comments
 (0)