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 src/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ impl Client {
let status = response.status();
let text = response.text().await?;
if status != 200 {
anyhow::bail!("Returning ({status}) not 200, body is {text}");
anyhow::bail!("broadcast failed with status:{status}, body is {text}");
}
let value: serde_json::Value = serde_json::from_str(&text)?;
let txid_text = value
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ pub struct AddressesRequest {
/// Requested page, 0 if not specified
/// The first returned index is equal to `page * 10000`
page: u16,

utxo_only: bool,
}

/// Response from the waterfalls endpoint
Expand Down
15 changes: 12 additions & 3 deletions src/server/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ fn parse_query(
Ok(WaterfallRequest::Addresses(AddressesRequest {
addresses,
page,
utxo_only,
}))
}
(None, None) => Err(Error::AtLeastOneFieldMandatory),
Expand Down Expand Up @@ -528,7 +529,7 @@ async fn handle_waterfalls_req(
.start_timer();

let mut map = BTreeMap::new();
let mut utxo_only_req = false;
let utxo_only_req;
let id;

match inputs {
Expand Down Expand Up @@ -584,14 +585,22 @@ async fn handle_waterfalls_req(
map.insert(desc.to_string(), result);
}
}
WaterfallRequest::Addresses(AddressesRequest { addresses, page: _ }) => {
WaterfallRequest::Addresses(AddressesRequest {
addresses,
page: _,
utxo_only,
}) => {
id = string_hash(&format!("{:?}", addresses));
utxo_only_req = utxo_only;
let mut scripts = Vec::with_capacity(addresses.len());
for addr in addresses.iter() {
scripts.push(db.hash(addr.script_pubkey().as_bytes()));
}
let mut result = Vec::with_capacity(addresses.len());
let _ = find_scripts(state, db, &mut result, scripts).await;
if utxo_only {
filter_utxo_only(&mut result, db)?;
}
map.insert("addresses".to_string(), result);
}
};
Expand All @@ -610,7 +619,7 @@ async fn handle_waterfalls_req(
tx_seen.block_timestamp = Some(ts);

if !utxo_only_req {
// setting v to 0 will avoid to serialize it since is not needed for full history scan
// setting v to undefined avoids to serialize it since is not needed for full history scan
tx_seen.v = V::Undefined;
}
}
Expand Down
16 changes: 15 additions & 1 deletion src/test_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@ impl WaterfallClient {
pub async fn waterfalls_addresses(
&self,
addressess: &[be::Address],
) -> anyhow::Result<(WaterfallResponse, HeaderMap)> {
self.waterfalls_addresses_utxo_only(addressess, false).await
}

pub async fn waterfalls_addresses_utxo_only(
&self,
addressess: &[be::Address],
utxo_only: bool,
) -> anyhow::Result<(WaterfallResponse, HeaderMap)> {
// this code is duplicated from waterfalls_version but we need to use the v3 endpoint which return a different object
let descriptor_url = format!("{}/v2/waterfalls", self.base_url);
Expand All @@ -404,10 +412,16 @@ impl WaterfallClient {
.map(|a| a.to_string())
.collect::<Vec<String>>()
.join(",");

let mut query_params = vec![("addresses", addresses_str)];
if utxo_only {
query_params.push(("utxo_only", "true".to_string()));
}

let response = self
.client
.get(&descriptor_url)
.query(&[("addresses", addresses_str)])
.query(&query_params)
.send()
.await?;

Expand Down
43 changes: 43 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,26 @@ async fn do_test(test_env: waterfalls::test_env::TestEnv) {
});
assert_eq!(result1, result2); // we didn't spend anything from the wallet, thus after zeroing v they are the same

// Test utxo_only on addresses endpoint, we just test the results are different because of v since we are not spending in this test
test_env.node_generate(1).await;
let mut result_utxo_only = client
.waterfalls_addresses_utxo_only(&vec![addr.clone()], true)
.await
.unwrap()
.0;
let mut result_full_history = client
.waterfalls_addresses(&vec![addr.clone()])
.await
.unwrap()
.0;
let full_txs = result_full_history.txs_seen.remove("addresses").unwrap().pop().unwrap();
let mut utxo_only_txs = result_utxo_only.txs_seen.remove("addresses").unwrap().pop().unwrap();
assert_ne!(full_txs, utxo_only_txs);
utxo_only_txs.iter_mut().for_each(|a| {
a.v = V::Undefined;
});
assert_eq!(full_txs, utxo_only_txs);

let is_unspent = client.unspent(&outpoint_for_unspent_check).await.unwrap();
assert!(!is_unspent, "UTXO should be spent");

Expand Down Expand Up @@ -679,6 +699,29 @@ async fn test_lwk_wollet() {
"utxo_only should have fewer transactions than regular scan"
);

// Test utxo_only on addresses endpoint with actual spending
// Test the address that received the initial funds - it should now have both incoming and outgoing transactions
let unconfidential_address = address.to_unconfidential().unwrap();

// Test with utxo_only=false (full history)
let result_full_history = test_env.client()
.waterfalls_addresses(&vec![unconfidential_address.clone()])
.await
.unwrap()
.0;

// Test with utxo_only=true (only transactions with unspent outputs)
let result_utxo_only = test_env.client()
.waterfalls_addresses_utxo_only(&vec![unconfidential_address.clone()], true)
.await
.unwrap()
.0;

let full_history_txs = &result_full_history.txs_seen.get("addresses").unwrap()[0];
let utxo_only_txs = &result_utxo_only.txs_seen.get("addresses").unwrap()[0];
assert_eq!(full_history_txs.len(), 2);
assert_eq!(utxo_only_txs.len(), 0);

test_env.shutdown().await;
assert!(true);
}
Expand Down