Skip to content
Draft
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
18 changes: 16 additions & 2 deletions packages/zaino-state/src/backends/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1866,7 +1866,14 @@ impl LightWalletIndexer for FetchServiceSubscriber {
request: GetAddressUtxosArg,
) -> Result<GetAddressUtxosReplyList, Self::Error> {
let taddrs = GetAddressBalanceRequest::new(request.addresses);
let utxos = self.z_get_address_utxos(taddrs).await?;
let utxos = self
.indexer
.get_address_utxos_bounded(
taddrs,
request.start_height,
(request.max_entries > 0).then_some(request.max_entries),
)
.await?;
let mut address_utxos: Vec<GetAddressUtxosReply> = Vec::new();
let mut entries: u32 = 0;
for utxo in utxos {
Expand Down Expand Up @@ -1919,7 +1926,14 @@ impl LightWalletIndexer for FetchServiceSubscriber {
request: GetAddressUtxosArg,
) -> Result<UtxoReplyStream, Self::Error> {
let taddrs = GetAddressBalanceRequest::new(request.addresses);
let utxos = self.z_get_address_utxos(taddrs).await?;
let utxos = self
.indexer
.get_address_utxos_bounded(
taddrs,
request.start_height,
(request.max_entries > 0).then_some(request.max_entries),
)
.await?;
let service_timeout = self.config.common.service.timeout;
let (channel_tx, channel_rx) =
mpsc::channel(self.config.common.service.channel_size as usize);
Expand Down
18 changes: 16 additions & 2 deletions packages/zaino-state/src/backends/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2512,7 +2512,14 @@ impl LightWalletIndexer for StateServiceSubscriber {
request: GetAddressUtxosArg,
) -> Result<GetAddressUtxosReplyList, Self::Error> {
let taddrs = GetAddressBalanceRequest::new(request.addresses);
let utxos = self.z_get_address_utxos(taddrs).await?;
let utxos = self
.indexer
.get_address_utxos_bounded(
taddrs,
request.start_height,
(request.max_entries > 0).then_some(request.max_entries),
)
.await?;
let mut address_utxos: Vec<GetAddressUtxosReply> = Vec::new();
let mut entries: u32 = 0;
for utxo in utxos {
Expand Down Expand Up @@ -2564,7 +2571,14 @@ impl LightWalletIndexer for StateServiceSubscriber {
request: GetAddressUtxosArg,
) -> Result<UtxoReplyStream, Self::Error> {
let taddrs = GetAddressBalanceRequest::new(request.addresses);
let utxos = self.z_get_address_utxos(taddrs).await?;
let utxos = self
.indexer
.get_address_utxos_bounded(
taddrs,
request.start_height,
(request.max_entries > 0).then_some(request.max_entries),
)
.await?;
let service_timeout = self.config.common.service.timeout;
let (channel_tx, channel_rx) =
mpsc::channel(self.config.common.service.channel_size as usize);
Expand Down
27 changes: 25 additions & 2 deletions packages/zaino-state/src/chain_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use arc_swap::ArcSwapOption;
use futures::{FutureExt, Stream};
use hex::FromHex as _;
use non_finalised_state::NonfinalizedBlockCacheSnapshot;
use source::{BlockchainSource, ValidatorConnector};
use source::{AddressUtxosRequest, BlockchainSource, ValidatorConnector};
use tokio_stream::StreamExt;
use tokio_util::sync::CancellationToken;
use tracing::{info, instrument};
Expand Down Expand Up @@ -513,6 +513,14 @@ pub trait ChainIndex {
address_strings: GetAddressBalanceRequest,
) -> impl std::future::Future<Output = Result<Vec<GetAddressUtxos>, Self::Error>>;

/// Returns bounded unspent transparent outputs for the given addresses.
fn get_address_utxos_bounded(
&self,
address_strings: GetAddressBalanceRequest,
start_height: u64,
max_entries: Option<u32>,
) -> impl std::future::Future<Output = Result<Vec<GetAddressUtxos>, Self::Error>>;

// ********** Metadata methods **********

/// Returns Information about the mempool state:
Expand Down Expand Up @@ -2301,9 +2309,24 @@ impl<Source: BlockchainSource> ChainIndex for NodeBackedChainIndexSubscriber<Sou
async fn get_address_utxos(
&self,
address_strings: GetAddressBalanceRequest,
) -> Result<Vec<GetAddressUtxos>, Self::Error> {
self.get_address_utxos_bounded(address_strings, 0, None)
.await
}

/// Returns bounded unspent transparent outputs for the given addresses.
async fn get_address_utxos_bounded(
&self,
address_strings: GetAddressBalanceRequest,
start_height: u64,
max_entries: Option<u32>,
) -> Result<Vec<GetAddressUtxos>, Self::Error> {
self.source()
.get_address_utxos(address_strings)
.get_address_utxos(AddressUtxosRequest::new(
address_strings,
start_height,
max_entries,
))
.await
.map_err(ChainIndexError::backing_validator)
}
Expand Down
38 changes: 37 additions & 1 deletion packages/zaino-state/src/chain_index/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ pub(crate) mod mockchain_source;
pub mod validator_connector;
pub use validator_connector::*;

/// Internal request for transparent UTXOs with lightwallet response bounds.
///
/// The backing `getaddressutxos` RPC only accepts addresses, so fetch-backed
/// sources still receive the full validator response before Zaino can apply
/// these bounds. State-backed sources can apply the bounds while converting the
/// state response into RPC-compatible UTXO rows.
#[derive(Clone, Debug)]
pub struct AddressUtxosRequest {
/// Addresses to query.
pub addresses: GetAddressBalanceRequest,
/// Ignore UTXOs below this block height.
pub start_height: u64,
/// Maximum number of UTXOs to return. `None` means unrestricted.
pub max_entries: Option<u32>,
}

impl AddressUtxosRequest {
/// Create a bounded UTXO query.
pub fn new(
addresses: GetAddressBalanceRequest,
start_height: u64,
max_entries: Option<u32>,
) -> Self {
Self {
addresses,
start_height,
max_entries,
}
}

/// Create the unbounded query used by the zcash-compatible RPC surface.
pub fn unbounded(addresses: GetAddressBalanceRequest) -> Self {
Self::new(addresses, 0, None)
}
}

/// A trait for accessing blockchain data from different backends.
///
/// TODO: Explore whether this should be split into separate capability based traits.
Expand Down Expand Up @@ -187,7 +223,7 @@ pub trait BlockchainSource: Clone + Send + Sync + 'static {
/// <https://github.com/zcash/lightwalletd/blob/master/frontend/service.go#L402>
async fn get_address_utxos(
&self,
address_strings: GetAddressBalanceRequest,
request: AddressUtxosRequest,
) -> BlockchainSourceResult<Vec<GetAddressUtxos>>;

// ********** Utility methods **********
Expand Down
14 changes: 12 additions & 2 deletions packages/zaino-state/src/chain_index/source/mockchain_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,9 +882,15 @@ impl BlockchainSource for MockchainSource {

async fn get_address_utxos(
&self,
address_strings: GetAddressBalanceRequest,
request: AddressUtxosRequest,
) -> BlockchainSourceResult<Vec<GetAddressUtxos>> {
let valid_addresses = address_strings.valid_addresses().map_err(|error| {
let AddressUtxosRequest {
addresses,
start_height,
max_entries,
} = request;

let valid_addresses = addresses.valid_addresses().map_err(|error| {
BlockchainSourceError::Unrecoverable(format!("invalid address: {error}"))
})?;

Expand All @@ -907,6 +913,10 @@ impl BlockchainSource for MockchainSource {

let utxos = unspent_outputs
.into_iter()
.filter(|(_outpoint, matching_output)| {
(matching_output.height.0 as u64) >= start_height
})
.take(max_entries.map_or(usize::MAX, |limit| limit as usize))
.map(|(_outpoint, matching_output)| {
GetAddressUtxos::new(
matching_output.address,
Expand Down
118 changes: 78 additions & 40 deletions packages/zaino-state/src/chain_index/source/validator_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -976,8 +976,14 @@ impl BlockchainSource for ValidatorConnector {

async fn get_address_utxos(
&self,
addresses: GetAddressBalanceRequest,
request: AddressUtxosRequest,
) -> BlockchainSourceResult<Vec<GetAddressUtxos>> {
let AddressUtxosRequest {
addresses,
start_height,
max_entries,
} = request;

match self {
ValidatorConnector::State(state) => {
let mut state = state.read_state_service.clone();
Expand All @@ -997,47 +1003,79 @@ impl BlockchainSource for ValidatorConnector {
let mut last_output_location =
zebra_state::OutputLocation::from_usize(zebra_chain::block::Height(0), 0, 0);

Ok(utxos
.utxos()
.map(
|(
utxo_address,
utxo_hash,
utxo_output_location,
utxo_transparent_output,
)| {
assert!(utxo_output_location > &last_output_location);
last_output_location = *utxo_output_location;
GetAddressUtxos::new(
utxo_address,
*utxo_hash,
utxo_output_location.output_index(),
utxo_transparent_output.lock_script.clone(),
u64::from(utxo_transparent_output.value()),
utxo_output_location.height(),
)
},
let mut address_utxos = Vec::new();

for (utxo_address, utxo_hash, utxo_output_location, utxo_transparent_output) in
utxos.utxos()
{
assert!(utxo_output_location > &last_output_location);
last_output_location = *utxo_output_location;

if (utxo_output_location.height().0 as u64) < start_height {
continue;
}
if let Some(limit) = max_entries {
if address_utxos.len() >= limit as usize {
break;
}
}

address_utxos.push(GetAddressUtxos::new(
utxo_address,
*utxo_hash,
utxo_output_location.output_index(),
utxo_transparent_output.lock_script.clone(),
u64::from(utxo_transparent_output.value()),
utxo_output_location.height(),
));
}

Ok(address_utxos)
}
ValidatorConnector::Fetch(fetch) => {
let rpc_utxos = fetch
.get_address_utxos(
addresses
.valid_addresses()
.map_err(|_error| {
BlockchainSourceError::Unrecoverable(
"Invalid address provided".to_string(),
)
})?
.into_iter()
.map(|address| address.to_string())
.collect(),
)
.collect())
.await
.map_err(|error| BlockchainSourceError::Unrecoverable(error.to_string()))?;

let mut address_utxos = Vec::new();

for utxo in rpc_utxos {
let (address, tx_hash, output_index, script, satoshis, height) =
GetAddressUtxos::from(utxo).into_parts();

if (height.0 as u64) < start_height {
continue;
}
if let Some(limit) = max_entries {
if address_utxos.len() >= limit as usize {
break;
}
}

address_utxos.push(GetAddressUtxos::new(
address,
tx_hash,
output_index,
script,
satoshis,
height,
));
}

Ok(address_utxos)
}
ValidatorConnector::Fetch(fetch) => Ok(fetch
.get_address_utxos(
addresses
.valid_addresses()
.map_err(|_error| {
BlockchainSourceError::Unrecoverable(
"Invalid address provided".to_string(),
)
})?
.into_iter()
.map(|address| address.to_string())
.collect(),
)
.await
.map_err(|error| BlockchainSourceError::Unrecoverable(error.to_string()))?
.into_iter()
.map(|utxos| utxos.into())
.collect()),
}
}

Expand Down
Loading
Loading