use std::sync::Arc;
use crate::{
chaindef::{OutPointHash, ScriptHash, TokenID},
indexes::outputindex::OutputIndexRow,
query::{
queryutil::{outpoints_are_spent, token_from_outpoint},
tip::get_utxos_at_tip,
BUFFER_SIZE, CHUNK_SIZE,
},
store::DBStore,
util::Channel,
};
use super::{
queryfilter::QueryFilter,
status::{scan_for_outputs, UnspentItem},
tip::UtxoEntry,
};
use anyhow::Result;
use futures::TryStreamExt;
use futures::{stream, StreamExt};
use rayon::slice::ParallelSliceMut;
pub async fn listunspent_at_tip(
confirmed: &Arc<DBStore>,
mempool: &Arc<DBStore>,
scripthash: &ScriptHash,
filter: &QueryFilter,
filter_token: &Option<TokenID>,
) -> Result<Vec<UnspentItem>> {
let channel: Channel<Option<UtxoEntry>> = Channel::bounded(1000);
let sender = channel.sender();
let unspent_task = tokio::spawn(async move {
let mut res = Vec::new();
let mut receiver = channel.receiver().await;
while let Some(Some((unspent, utxo, token))) = receiver.recv().await {
res.push(UnspentItem {
txid: utxo.txid(),
vout: utxo.index(),
outpoint_hash: Some(unspent.outpointhash()),
height: utxo.height(),
value: utxo.value(),
token_amount: token.as_ref().map(|t| t.1),
has_token: unspent.has_token(),
#[cfg(bch)]
commitment: token.as_ref().and_then(|t| t.2.clone()),
token_id: token.map(|t| t.0),
});
}
res
});
get_utxos_at_tip(confirmed, mempool, scripthash, filter, filter_token, sender).await?;
let mut utxos = unspent_task.await?;
utxos.par_sort_by_key(|u| u.height);
Ok(utxos)
}
pub async fn lisunspent_full_search(
confirmed: &Arc<DBStore>,
mempool: &Arc<DBStore>,
scripthash: &ScriptHash,
filter: &QueryFilter,
filter_token: &Option<TokenID>,
) -> Result<Vec<UnspentItem>> {
let (confirmed_query, confirmed_stream) =
scan_for_outputs(confirmed, *scripthash, filter).await;
let confirmed_utxos = confirmed_stream
.ready_chunks(CHUNK_SIZE)
.then(|outputs| {
let outpoints: Vec<OutPointHash> = outputs.iter().map(|(o, _)| o.hash()).collect();
let confirmed = confirmed.clone();
let unconfirmed = mempool.clone();
async move {
let is_spent = outpoints_are_spent(&confirmed, &outpoints).await;
let outputs: Vec<(OutputIndexRow, bool)> = outputs
.into_iter()
.zip(is_spent)
.filter_map(|(o, is_spent)| if is_spent { None } else { Some(o) })
.collect();
let outpoints: Vec<OutPointHash> = outputs.iter().map(|(o, _)| o.hash()).collect();
let is_spent = outpoints_are_spent(&unconfirmed, &outpoints).await;
stream::iter(
outputs
.into_iter()
.zip(is_spent)
.filter_map(|(o, is_spent)| if is_spent { None } else { Some(o) }),
)
}
})
.flatten_unordered(BUFFER_SIZE);
let (unconfirmed_query, unconfirmed_stream) =
scan_for_outputs(mempool, *scripthash, filter).await;
let unconfirmed =
unconfirmed_stream
.ready_chunks(CHUNK_SIZE)
.then(|outputs| {
let outpoints: Vec<OutPointHash> = outputs.iter().map(|(o, _)| o.hash()).collect();
let mempool = Arc::clone(mempool);
async move {
let is_spent = outpoints_are_spent(&mempool, &outpoints).await;
stream::iter(outputs.into_iter().zip(is_spent).map(
|((o, has_token), is_spent)| {
if is_spent {
None
} else {
Some((o, has_token))
}
},
))
}
})
.flatten_unordered(BUFFER_SIZE)
.filter_map(|item| async move { item });
let unspent_outputs = confirmed_utxos.chain(unconfirmed);
let unspent_outputs = unspent_outputs.filter_map(|(output, has_token)| {
let filter_token = filter_token.clone();
let (confirmed, unconfirmed) = if has_token && filter.token_only {
(Some(Arc::clone(confirmed)), Some(Arc::clone(mempool)))
} else {
(None, None)
};
async move {
if !has_token || !filter.token_only {
return Some(Ok((output, None, None, has_token, None)));
}
let outpointhash = output.hash();
let token = match token_from_outpoint(&confirmed.unwrap(), &outpointhash).await {
std::result::Result::Ok(t) => t,
Err(e) => return Some(Err(e)),
};
let (token_id, amount, commitment) = if let Some(t) = token {
t
} else {
let t = match token_from_outpoint(&unconfirmed.unwrap(), &outpointhash).await {
std::result::Result::Ok(t) => t,
Err(e) => return Some(Err(e)),
};
match t {
Some(t) => t,
None => return Some(Err(anyhow!("expected to find token output"))),
}
};
if let Some(filter) = &filter_token {
if filter == &token_id {
Some(Ok((
output,
Some(token_id),
Some(amount),
has_token,
commitment,
)))
} else {
None
}
} else {
Some(Ok((
output,
Some(token_id),
Some(amount),
has_token,
commitment,
)))
}
}
});
#[allow(unused_variables)] #[allow(clippy::type_complexity)]
let unspent_outputs = unspent_outputs.map(|output| {
let (out, token_id, token_amount, has_token, commitment): (
OutputIndexRow,
Option<TokenID>,
Option<i64>,
bool,
Option<Vec<u8>>,
) = output?;
anyhow::Ok({
UnspentItem {
txid: out.txid(),
vout: out.index(),
outpoint_hash: Some(out.hash()),
height: out.height(),
value: out.value(),
token_id,
token_amount,
has_token,
#[cfg(bch)]
commitment,
}
})
});
let mut unspent: Vec<UnspentItem> = unspent_outputs.try_collect().await?;
confirmed_query.await??;
unconfirmed_query.await??;
unspent.par_sort_by_key(|u| u.height);
Ok(unspent)
}