use std::collections::HashSet;
use anyhow::Result;
use bitcoincash::Txid;
use futures::stream::StreamExt;
use futures_util::future;
#[cfg(nexa)]
use std::sync::Arc;
use crate::{
chaindef::{TokenID, Transaction},
indexes::{
heightindex::HeightIndexRow, inputindex::InputIndexRow, outputindex::OutputIndexRow,
tokenoutputindex::TokenOutputIndexRow, DBRow,
},
store::DBStore,
};
use super::{queryutil::get_row, tx::TxQuery, BUFFER_SIZE};
#[allow(unused_variables)]
pub(crate) async fn get_token_history(
token_id: &TokenID,
commitment: &Option<Vec<u8>>,
store: &DBStore,
) -> Result<Vec<(Txid, u32)>> {
let to_outpoint_filter = {
#[cfg(bch)]
{
match commitment {
Some(c) => TokenOutputIndexRow::filter_by_parent_and_subgroup(token_id, c),
None => TokenOutputIndexRow::filter_by_parent_including_subroup(token_id),
}
}
#[cfg(nexa)]
TokenOutputIndexRow::filter_by_parent_and_subgroup(token_id)
};
let (query, token_stream) = store
.scan(TokenOutputIndexRow::CF, to_outpoint_filter, None)
.await;
let txids: HashSet<Txid> = token_stream
.map(|r| TokenOutputIndexRow::from_row(&r))
.map(|r| async move {
let oph = r.outpoint_hash();
let funding_txid =
get_row::<OutputIndexRow>(store, OutputIndexRow::filter_by_outpointhash(&oph))
.await
.expect("txid not found for token output")
.txid();
let spending_txid =
get_row::<InputIndexRow>(store, InputIndexRow::filter_by_outpointhash(&oph))
.await
.map(|row| row.txid());
std::iter::once(funding_txid).chain(spending_txid)
})
.buffer_unordered(BUFFER_SIZE)
.flat_map(futures::stream::iter)
.collect()
.await;
query.await??;
let mut h: Vec<(Txid, u32)> = futures::stream::iter(txids.into_iter())
.map(|txid| async move {
let height = get_row::<HeightIndexRow>(store, HeightIndexRow::filter_by_txid(&txid))
.await
.expect("height not found for txid")
.height();
(txid, height)
})
.buffer_unordered(BUFFER_SIZE)
.collect()
.await;
h.sort_unstable_by(|a, b| match b.1.cmp(&a.1) {
std::cmp::Ordering::Less => std::cmp::Ordering::Less,
std::cmp::Ordering::Equal => b.0.cmp(&a.0),
std::cmp::Ordering::Greater => std::cmp::Ordering::Greater,
});
Ok(h)
}
#[cfg(bch)]
pub(crate) async fn find_token_genesis(
token_id: &TokenID,
confirmed_index: &DBStore,
unconfirmed_index: &DBStore,
txquery: &TxQuery,
) -> Result<(Transaction, i64)> {
use anyhow::Context;
use bitcoin_hashes::Hash;
use crate::{encode::compute_outpoint_hash, errors::rpc_invalid_params};
let genesis_outpoint = compute_outpoint_hash(&Txid::from_inner(token_id.into_inner()), 0);
let key = InputIndexRow::filter_by_outpointhash(&genesis_outpoint);
let spender = match get_row::<InputIndexRow>(confirmed_index, key.clone()).await {
Some(spender) => Some(spender),
None => get_row::<InputIndexRow>(unconfirmed_index, key).await,
}
.context(rpc_invalid_params("Token genesis not found".to_string()))?;
debug_assert!(spender.index() == 0);
let genesis_txid = spender.txid();
let height = txquery
.get_confirmation_height(&genesis_txid)
.await
.context(rpc_invalid_params(
"Genesis transaction not found".to_string(),
))?;
Ok((
txquery
.get(
&genesis_txid,
None,
if height > 0 {
Some(height as u32)
} else {
None
},
false,
)
.await?,
height,
))
}
#[cfg(nexa)]
pub(crate) async fn find_token_genesis(
token_id: &TokenID,
store: &Arc<DBStore>,
txquery: &TxQuery,
) -> Result<(Transaction, i64)> {
use futures_util::stream;
use crate::{indexes::outputtokenindex::OutputTokenIndexRow, query::CHUNK_SIZE};
let to_outpoint_filter = TokenOutputIndexRow::filter_by_parent_and_subgroup(token_id);
let (query, token_stream) = store
.scan(TokenOutputIndexRow::CF, to_outpoint_filter, None)
.await;
let lowest_height_txs: Vec<(Txid, u32)> = token_stream
.chunks(CHUNK_SIZE)
.then(|rows| {
let store = Arc::clone(store);
async move {
let txid_keys: Vec<Vec<u8>> = rows
.iter()
.map(TokenOutputIndexRow::from_row)
.map(|r| OutputIndexRow::filter_by_outpointhash(&r.outpoint_hash()))
.collect();
let txids: Vec<(Txid, u32)> = store
.multi_get(OutputIndexRow::CF, txid_keys)
.await
.into_iter()
.map(|row| {
OutputIndexRow::txid_and_height_from_value(
&row.expect("txid not found for token output"),
)
.expect("failed to parse txid for token output")
})
.collect();
stream::iter(txids.into_iter())
}
})
.flatten_unordered(BUFFER_SIZE)
.fold(
Vec::new(),
|lowest: Vec<(Txid, u32)>, tx: (Txid, u32)| async move {
if lowest.is_empty() {
vec![tx]
} else {
match tx.1.cmp(&lowest[0].1) {
std::cmp::Ordering::Less => vec![tx],
std::cmp::Ordering::Equal => {
lowest.into_iter().chain(std::iter::once(tx)).collect()
}
std::cmp::Ordering::Greater => lowest,
}
}
},
)
.await;
query.await??;
for (txid, height) in lowest_height_txs {
assert!(height > 0);
let tx = txquery.get(&txid, None, Some(height), false).await?;
if stream::iter(tx.input.iter())
.any(|i| async move {
let outpoint_hash = &i.previous_output.hash;
let key = OutputTokenIndexRow::filter_by_outpointhash_and_token(
outpoint_hash,
token_id.clone(),
);
store.exists(OutputTokenIndexRow::CF, key).await
})
.await
{
continue;
} else {
return Ok((tx, height as i64));
}
}
bail!("Token {} not found", token_id.to_hex())
}
pub(crate) async fn list_nft(
token_id: &TokenID,
store: &DBStore,
) -> Result<HashSet<([u8; 32], Vec<u8>)>> {
let to_outpoint_filter = TokenOutputIndexRow::filter_by_parent_including_subroup(token_id);
let (query, stream) = store
.scan(TokenOutputIndexRow::CF, to_outpoint_filter, None)
.await;
let nfts = stream
.map(|r| TokenOutputIndexRow::from_row(&r))
.filter(|r| future::ready(r.is_subtoken()))
.map(|r| r.into_parent_and_subgroup())
.collect()
.await;
query.await??;
Ok(nfts)
}