#![allow(unused_imports)]
#![cfg(all(feature = "blocking", feature = "async", feature = "tokio"))]
use bitcoin::hashes::Hash;
use bitcoin::Amount;
use bitcoin::Transaction;
use bitcoin::Txid;
use electrsd::bitcoind::get_available_port;
use std::collections::HashMap;
use tokio::io::AsyncReadExt;
use tokio::net::TcpListener;
use testenv::TestEnv;
mod testenv;
#[tokio::test]
async fn test_get_tx() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let tx = blocking_client.get_tx(&txid).unwrap();
let tx_async = async_client.get_tx(&txid).await.unwrap();
assert_eq!(tx, tx_async);
}
#[tokio::test]
async fn test_get_tx_no_opt() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let tx_no_opt = blocking_client.get_tx_no_opt(&txid).unwrap();
let tx_no_opt_async = async_client.get_tx_no_opt(&txid).await.unwrap();
assert_eq!(tx_no_opt, tx_no_opt_async);
}
#[tokio::test]
async fn test_get_tx_status() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let tx_status = blocking_client.get_tx_status(&txid).unwrap();
let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
assert_eq!(tx_status, tx_status_async);
assert!(tx_status.confirmed);
let txid = Txid::hash(b"ayyyy lmao");
let tx_status = blocking_client.get_tx_status(&txid).unwrap();
let tx_status_async = async_client.get_tx_status(&txid).await.unwrap();
assert_eq!(tx_status, tx_status_async);
assert!(!tx_status.confirmed);
assert!(tx_status.block_height.is_none());
assert!(tx_status.block_hash.is_none());
assert!(tx_status.block_time.is_none());
}
#[tokio::test]
async fn test_get_tx_info() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let tx_res = env
.bitcoind_client()
.get_transaction(txid)
.unwrap()
.into_model()
.unwrap();
let tx_exp: Transaction = tx_res.tx;
let tx_block_height = env
.bitcoind_client()
.get_block_header_verbose(&tx_res.block_hash.unwrap())
.unwrap()
.into_model()
.unwrap()
.height;
let tx_info = blocking_client
.get_tx_info(&txid)
.unwrap()
.expect("must get tx");
let tx_info_async = async_client
.get_tx_info(&txid)
.await
.unwrap()
.expect("must get tx");
assert_eq!(tx_info, tx_info_async);
assert_eq!(tx_info.txid, txid);
assert_eq!(tx_info.to_tx(), tx_exp);
assert_eq!(tx_info.size, tx_exp.total_size());
assert_eq!(tx_info.weight, tx_exp.weight());
assert_eq!(tx_info.fee, tx_res.fee.unwrap().unsigned_abs());
assert!(tx_info.status.confirmed);
assert_eq!(tx_info.status.block_height, Some(tx_block_height));
assert_eq!(tx_info.status.block_hash, tx_res.block_hash);
assert_eq!(
tx_info.status.block_time,
tx_res.block_time.map(|bt| bt as u64)
);
let txid = Txid::hash(b"not exist");
assert_eq!(blocking_client.get_tx_info(&txid).unwrap(), None);
assert_eq!(async_client.get_tx_info(&txid).await.unwrap(), None);
}
#[tokio::test]
async fn test_get_tx_merkle_proof() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let merkle_proof = blocking_client.get_merkle_proof(&txid).unwrap().unwrap();
let merkle_proof_async = async_client.get_merkle_proof(&txid).await.unwrap().unwrap();
assert_eq!(merkle_proof, merkle_proof_async);
assert!(merkle_proof.pos > 0);
}
#[tokio::test]
async fn test_get_tx_output_status() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let output_status = blocking_client
.get_output_status(&txid, 1)
.unwrap()
.unwrap();
let output_status_async = async_client
.get_output_status(&txid, 1)
.await
.unwrap()
.unwrap();
assert_eq!(output_status, output_status_async);
}
#[tokio::test]
async fn test_get_height() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let block_height = blocking_client.get_height().unwrap();
let block_height_async = async_client.get_height().await.unwrap();
assert!(block_height > 0);
assert_eq!(block_height, block_height_async);
}
#[tokio::test]
async fn test_get_tip_hash() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let tip_hash = blocking_client.get_tip_hash().unwrap();
let tip_hash_async = async_client.get_tip_hash().await.unwrap();
assert_eq!(tip_hash, tip_hash_async);
}
#[tokio::test]
async fn test_get_block_hash() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let block_hash = env
.bitcoind_client()
.get_block_hash(21)
.unwrap()
.block_hash()
.unwrap();
let block_hash_blocking = blocking_client.get_block_hash(21).unwrap();
let block_hash_async = async_client.get_block_hash(21).await.unwrap();
assert_eq!(block_hash, block_hash_blocking);
assert_eq!(block_hash, block_hash_async);
}
#[tokio::test]
async fn test_broadcast() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
let tx = env
.bitcoind_client()
.get_transaction(txid)
.expect("tx should exist for given `txid`")
.into_model()
.expect("should convert successfully")
.tx;
let blocking_res = blocking_client
.broadcast(&tx)
.expect("should successfully broadcast tx");
let async_res = async_client
.broadcast(&tx)
.await
.expect("should successfully broadcast tx");
assert_eq!(blocking_res, txid);
assert_eq!(async_res, txid);
}
#[tokio::test]
async fn test_get_tx_outspends() {
let env = TestEnv::new();
let (blocking_client, async_client) = env.setup_clients();
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(21000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let outspends_blocking = blocking_client.get_tx_outspends(&txid).unwrap();
let outspends_async = async_client.get_tx_outspends(&txid).await.unwrap();
assert_eq!(outspends_blocking.len(), 2);
assert_eq!(outspends_async.len(), 2);
assert_eq!(outspends_blocking, outspends_async);
assert!(outspends_blocking.iter().all(|output| !output.spent));
}
#[tokio::test]
async fn test_get_tx_with_http_headers() {
async fn handle_requests(listener: TcpListener, count: usize) -> Vec<[u8; 4096]> {
let mut raw_requests = vec![];
for _ in 0..count {
let (mut stream, _) = listener.accept().await.expect("should accept connection!");
let mut buf = [0u8; 4096];
AsyncReadExt::read(&mut stream, &mut buf)
.await
.expect("should read from stream");
raw_requests.push(buf);
}
raw_requests
}
let base_url = format!(
"127.0.0.1:{}",
get_available_port().expect("should get an available port successfully!")
);
let listener = TcpListener::bind(&base_url)
.await
.expect("should bind the TCP listener successfully");
let env = TestEnv::new();
let exp_header_key = "Authorization";
let exp_header_value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
let headers = HashMap::from([(exp_header_key.to_string(), exp_header_value.to_string())]);
let (blocking_client, async_client) = env.setup_clients_with_headers(&base_url, headers);
let address = env.get_legacy_address();
let txid = env
.bitcoind_client()
.send_to_address(&address, Amount::from_sat(1000))
.unwrap()
.txid()
.unwrap();
env.mine_and_wait(1);
let blocking_task = tokio::task::spawn_blocking(move || blocking_client.get_tx(&txid));
let async_task = tokio::task::spawn(async move { async_client.get_tx(&txid).await });
let raw_requests = handle_requests(listener, 2).await;
let requests = raw_requests
.iter()
.map(|raw| {
String::from_utf8(raw.to_vec()).expect("should parse HTTP requests successfully")
})
.collect::<Vec<String>>();
assert_eq!(
requests.len(),
2,
"it MUST contain ONLY two requests (i.e a single one from each client)"
);
let assert_request = |user_agent: &str, header_key: &str| {
let expected_path = format!("GET /tx/{txid}/raw");
let expected_auth = format!("{header_key}: {exp_header_value}");
assert!(
requests.iter().any(|req| {
req.contains(&expected_path)
&& req.contains(&expected_auth)
&& req.contains(user_agent)
}),
"request MUST call `{expected_path}` with `{user_agent}` and expected authorization header"
);
};
assert_request("User-Agent: blocking", exp_header_key);
assert_request("User-Agent: async", exp_header_key);
let _ = blocking_task.await.expect("blocking task should not panic");
let _ = async_task.await.expect("async task should not panic");
}