Skip to content
Merged
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
2 changes: 1 addition & 1 deletion 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 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
88 changes: 87 additions & 1 deletion src/chain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,14 +934,100 @@ impl Filter for ElectrumRuntimeClient {

#[cfg(test)]
mod tests {
use std::io::{BufRead, BufReader, Write};
use std::net::{Shutdown, 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;
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let server_address = listener.local_addr().unwrap();
let (client_sender, client_receiver) = mpsc::channel::<Arc<ElectrumClient>>();
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"],
});
Comment thread
tankyleo marked this conversation as resolved.
writeln!(reader.get_mut(), "{version_response}").unwrap();
reader.get_mut().flush().unwrap();
let client = client_receiver.recv().unwrap();

// The client's reader thread holds the shared stream while it blocks on a read, so
// other requests can't be written meanwhile. Send notifications to repeatedly
// unblock it until every request was fully written and flushed (excluding the
// version handshake).
let nudge = serde_json::json!({"jsonrpc": "2.0", "method": "test.nudge", "params": []});
let deadline = std::time::Instant::now() + Duration::from_secs(30);
while client.calls_made().unwrap() - 1 < REQUEST_COUNT {
assert!(std::time::Instant::now() < deadline, "requests weren't all sent");
writeln!(reader.get_mut(), "{nudge}").unwrap();
thread::sleep(Duration::from_millis(10));
}
drop(client);
// Give the requests time to start waiting for their responses.
thread::sleep(Duration::from_millis(50));

// Only shut down our sending side so the client observes a clean EOF. Closing the
// entire socket with unread requests could send a TCP reset instead, which older
// clients already handled.
reader.get_ref().shutdown(Shutdown::Write).unwrap();

// Keep reading until the client hangs up so unread requests don't trigger a reset.
reader.get_mut().set_read_timeout(Some(Duration::from_secs(10))).unwrap();
let mut remaining = String::new();
while matches!(reader.read_line(&mut remaining), Ok(n) if n > 0) {
remaining.clear();
}
});

let config = ElectrumConfigBuilder::new().retry(0).build();
let client = Arc::new(
ElectrumClient::from_config(&format!("tcp://{server_address}"), config).unwrap(),
);
client_sender.send(Arc::clone(&client)).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();

for _ in 0..REQUEST_COUNT {
let failed_as_expected = finished_receiver
.recv_timeout(Duration::from_secs(60))
.expect("clean EOF must wake every concurrent Electrum request");
assert!(failed_as_expected);
}
for request in requests {
request.join().unwrap();
}
// Hang up so the server stops reading.
drop(client);
server.join().unwrap();
}

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