Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightn
bdk_chain = { version = "0.23.3", default-features = false, features = ["std"] }
bdk_esplora = { version = "0.22.2", default-features = false, features = ["async-https-rustls", "tokio"], optional = true }
bdk_electrum = { version = "0.24.0", default-features = false, features = ["use-rustls-ring"], optional = true }
bdk_wallet = { version = "3.1.0", default-features = false, features = ["std", "keys-bip39"]}
bdk_wallet = { version = "3.2.0", default-features = false, features = ["std", "keys-bip39"]}

bitreq = { version = "0.3", default-features = false, features = ["async-https", "json-using-serde"] }
rustls = { version = "0.23", default-features = false }
Expand All @@ -118,7 +118,7 @@ tokio = { version = "1.39", default-features = false, features = [ "rt-multi-thr
tokio-util = { version = "0.7.10", default-features = false, features = ["rt"] }
esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"], optional = true }
ldk-esplora-client = { package = "esplora-client", version = "0.13", default-features = false, features = ["tokio", "async-https-rustls"], optional = true }
electrum-client = { version = "0.25", default-features = false, features = ["proxy", "use-rustls-ring"], optional = true }
electrum-client = { version = "0.25.1", default-features = false, features = ["proxy", "use-rustls-ring"], optional = true }
libc = "0.2"
uniffi = { version = "0.29.5", features = ["build"], optional = true }
serde = { version = "1.0.210", default-features = false, features = ["std", "derive"] }
Expand Down
24 changes: 20 additions & 4 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use std::path::PathBuf;
use std::sync::{Arc, Mutex, Once, RwLock};
use std::time::SystemTime;

use bdk_wallet::descriptor::IntoWalletDescriptor;
use bdk_wallet::signer::SignersContainer;
use bdk_wallet::template::Bip84;
use bdk_wallet::{KeychainKind, Wallet as BdkWallet};
use bitcoin::bip32::{ChildNumber, Xpriv};
Expand Down Expand Up @@ -1758,17 +1760,30 @@ fn build_with_store_internal(
BuildError::WalletSetupFailed
})?;

let descriptor = Bip84(xprv, KeychainKind::External);
let change_descriptor = Bip84(xprv, KeychainKind::Internal);
let secp_ctx = Secp256k1::new();
let (descriptor, keymap) = Bip84(xprv, KeychainKind::External)
.into_wallet_descriptor(&secp_ctx, config.network.into())
.map_err(|e| {
log_error!(logger, "Failed to derive external wallet descriptor: {}", e);
BuildError::WalletSetupFailed
})?;
let (change_descriptor, change_keymap) = Bip84(xprv, KeychainKind::Internal)
.into_wallet_descriptor(&secp_ctx, config.network.into())
.map_err(|e| {
log_error!(logger, "Failed to derive internal wallet descriptor: {}", e);
BuildError::WalletSetupFailed
})?;
let signers = [
SignersContainer::build(keymap, &descriptor, &secp_ctx),
SignersContainer::build(change_keymap, &change_descriptor, &secp_ctx),
];
let mut wallet_persister =
KVStoreWalletPersister::new(Arc::clone(&kv_store), Arc::clone(&logger));
#[allow(deprecated)]
let wallet_opt = runtime
.block_on(async {
BdkWallet::load()
.descriptor(KeychainKind::External, Some(descriptor.clone()))
.descriptor(KeychainKind::Internal, Some(change_descriptor.clone()))
.extract_keys()
.check_network(config.network)
.load_wallet_async(&mut wallet_persister)
.await
Expand Down Expand Up @@ -1957,6 +1972,7 @@ fn build_with_store_internal(

let wallet = Arc::new(Wallet::new(
bdk_wallet,
signers,
wallet_persister,
persisted_pool_indices,
Arc::clone(&tx_broadcaster),
Expand Down
86 changes: 85 additions & 1 deletion src/chain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,14 +919,98 @@ impl Filter for ElectrumRuntimeClient {

#[cfg(test)]
mod tests {
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc;
use std::sync::{mpsc, Barrier};
use std::thread;

use bitcoin::blockdata::constants::genesis_block;

use super::*;

#[test]
fn clean_eof_wakes_concurrent_electrum_requests() {
const REQUEST_COUNT: usize = 8;
for _ in 0..10 {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let server_address = listener.local_addr().unwrap();
let server = thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
let mut reader = BufReader::new(stream);
let mut version_request = String::new();
assert!(reader.read_line(&mut version_request).unwrap() > 0);
let version_request: serde_json::Value =
serde_json::from_str(&version_request).unwrap();
let version_response = serde_json::json!({
"jsonrpc": "2.0",
"id": version_request["id"],
"result": ["test-server", "1.4"],
});
writeln!(reader.get_mut(), "{version_response}").unwrap();
reader.get_mut().flush().unwrap();
reader.get_mut().set_read_timeout(Some(Duration::from_millis(250))).unwrap();

let mut requests_received = 0;
while requests_received < 2 {
let mut request = String::new();
match reader.read_line(&mut request) {
Ok(0) => break,
Ok(_) => requests_received += 1,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let’s also ensure multiple requests are fully sent before shutting down the server’s sending side. Otherwise, the remaining requests could fail while writing, allowing the test to pass without verifying that EOF wakes multiple requests waiting for responses.

Err(e)
if matches!(
e.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) =>
{
break
},
Err(e) => panic!("failed to read Electrum request: {e}"),
}
}
if requests_received > 1 {
thread::sleep(Duration::from_millis(50));
}
requests_received
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here we close the entire socket while request data may still be unread. This can send a TCP reset, which the old client already handles correctly, allowing the test to pass without exercising the EOF bug.

We should shut down only the server’s sending side first, then read the remaining requests before fully closing the socket.


let config = ElectrumConfigBuilder::new().retry(0).build();
let client = Arc::new(
ElectrumClient::from_config(&format!("tcp://{server_address}"), config).unwrap(),
);
let start = Arc::new(Barrier::new(REQUEST_COUNT + 1));
let (finished_sender, finished_receiver) = mpsc::channel();
let mut requests = Vec::new();
for _ in 0..REQUEST_COUNT {
let client = Arc::clone(&client);
let start = Arc::clone(&start);
let finished_sender = finished_sender.clone();
requests.push(thread::spawn(move || {
start.wait();
let result =
client.raw_call("server.ping", Vec::<electrum_client::Param>::new());
finished_sender.send(result.is_err()).unwrap();
}));
}
start.wait();
let requests_received = server.join().unwrap();

for _ in 0..REQUEST_COUNT {
let failed_as_expected = finished_receiver
.recv_timeout(Duration::from_secs(5))
.expect("clean EOF must wake every concurrent Electrum request");
assert!(failed_as_expected);
}
for request in requests {
request.join().unwrap();
}
if requests_received > 1 {
return;
}
}
panic!("failed to issue concurrent Electrum requests");
}

struct RecordingConfirm {
calls: AtomicUsize,
relevant_txid: Txid,
Expand Down
2 changes: 0 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use bdk_chain::bitcoin::psbt::ExtractTxError as BdkExtractTxError;
use bdk_chain::local_chain::CannotConnectError as BdkChainConnectionError;
use bdk_chain::tx_graph::CalculateFeeError as BdkChainCalculateFeeError;
use bdk_wallet::error::CreateTxError as BdkCreateTxError;
#[allow(deprecated)]
use bdk_wallet::signer::SignerError as BdkSignerError;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -255,7 +254,6 @@ impl fmt::Display for Error {

impl std::error::Error for Error {}

#[allow(deprecated)]
impl From<BdkSignerError> for Error {
fn from(_: BdkSignerError) -> Self {
Self::OnchainTxSigningFailed
Expand Down
Loading
Loading