Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change(state): Set upper bound when reading from deleting column family tx_loc_by_transparent_addr_loc #7732

Merged
merged 9 commits into from
Oct 18, 2023
32 changes: 24 additions & 8 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use zebra_chain::{
};
use zebra_node_services::BoxError;

use zebra_state::{LatestChainTip, ReadStateService};
use zebra_test::mock_service::MockService;

use super::super::*;
Expand Down Expand Up @@ -674,33 +675,48 @@ async fn rpc_getaddresstxids_response() {
.address(network)
.unwrap();

// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.to_owned(), network).await;

if network == Mainnet {
// Exhaustively test possible block ranges for mainnet.
//
// TODO: if it takes too long on slower machines, turn this into a proptest with 10-20 cases
for start in 1..=10 {
for end in start..=10 {
rpc_getaddresstxids_response_with(network, start..=end, &blocks, &address)
.await;
rpc_getaddresstxids_response_with(
network,
start..=end,
&address,
&read_state,
&latest_chain_tip,
)
.await;
}
}
} else {
// Just test the full range for testnet.
rpc_getaddresstxids_response_with(network, 1..=10, &blocks, &address).await;
rpc_getaddresstxids_response_with(
network,
1..=10,
&address,
&read_state,
&latest_chain_tip,
)
.await;
}
}
}

async fn rpc_getaddresstxids_response_with(
network: Network,
range: RangeInclusive<u32>,
blocks: &[Arc<Block>],
address: &transparent::Address,
read_state: &ReadStateService,
latest_chain_tip: &LatestChainTip,
) {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
// Create a populated state service
let (_state, read_state, latest_chain_tip, _chain_tip_change) =
zebra_state::populated_state(blocks.to_owned(), network).await;

let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
"RPC test",
Expand All @@ -710,7 +726,7 @@ async fn rpc_getaddresstxids_response_with(
true,
Buffer::new(mempool.clone(), 1),
Buffer::new(read_state.clone(), 1),
latest_chain_tip,
latest_chain_tip.clone(),
);

// call the method with valid arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ pub struct TransactionLocation {

impl TransactionLocation {
/// Creates a transaction location from a block height and transaction index.
#[allow(dead_code)]
pub fn from_index(height: Height, transaction_index: u16) -> TransactionLocation {
TransactionLocation {
height,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,36 +416,36 @@ impl AddressTransaction {
}
}

/// Create an [`AddressTransaction`] which starts iteration for the supplied
/// Create a range of [`AddressTransaction`]s which starts iteration for the supplied
/// address. Starts at the first UTXO, or at the `query_start` height,
/// whichever is greater.
/// whichever is greater. Ends at the last transaction index for an [`AddressLocation`].
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
///
/// Used to look up the first transaction with
/// [`ReadDisk::zs_next_key_value_from`][1].
/// Used to look up transactions with
/// [`DiskDb::zs_range_iter`][1].
///
/// The transaction location might be invalid, if it is based on the
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
/// `query_start` height. But this is not an issue, since
/// [`ReadDisk::zs_next_key_value_from`][1] will fetch the next existing
/// (valid) value.
/// [`DiskDb::zs_range_iter`][1] will fetch all existing
/// (valid) values in the range.
///
/// [1]: super::super::disk_db::ReadDisk::zs_next_key_value_from
pub fn address_iterator_start(
/// [1]: super::super::disk_db::DiskDb
pub fn address_iterator_range(
address_location: AddressLocation,
query_start: Height,
) -> AddressTransaction {
query: std::ops::RangeInclusive<Height>,
) -> std::ops::RangeInclusive<AddressTransaction> {
// Iterating from the lowest possible transaction location gets us the first transaction.
//
// The address location is the output location of the first UTXO sent to the address,
// and addresses can not spend funds until they receive their first UTXO.
let first_utxo_location = address_location.transaction_location();

// Iterating from the start height filters out transactions that aren't needed.
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
let query_start_location = TransactionLocation::from_usize(query_start, 0);
let query_start_location = TransactionLocation::from_index(*query.start(), 0);
let query_end_location = TransactionLocation::from_index(*query.end(), u16::MAX);

AddressTransaction {
address_location,
transaction_location: max(first_utxo_location, query_start_location),
}
let addr_tx = |tx_loc| AddressTransaction::new(address_location, tx_loc);

addr_tx(max(first_utxo_location, query_start_location))..=addr_tx(query_end_location)
}

/// Update the transaction location to the next possible transaction for the
Expand All @@ -457,6 +457,7 @@ impl AddressTransaction {
/// existing (valid) value.
///
/// [1]: super::super::disk_db::ReadDisk::zs_next_key_value_from
#[allow(dead_code)]
pub fn address_iterator_next(&mut self) {
// Iterating from the next possible output location gets us the next output,
// even if it is in a later block or transaction.
Expand Down
41 changes: 6 additions & 35 deletions zebra-state/src/service/finalized_state/zebra_db/transparent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,44 +235,15 @@ impl ZebraDb {
let tx_loc_by_transparent_addr_loc =
self.db.cf_handle("tx_loc_by_transparent_addr_loc").unwrap();

// Manually fetch the entire addresses' transaction locations
let mut addr_transactions = BTreeSet::new();

// A potentially invalid key representing the first UTXO send to the address,
// or the query start height.
let mut transaction_location = AddressTransaction::address_iterator_start(
address_location,
*query_height_range.start(),
);

loop {
// Seek to a valid entry for this address, or the first entry for the next address
transaction_location = match self
.db
.zs_next_key_value_from(&tx_loc_by_transparent_addr_loc, &transaction_location)
{
Some((transaction_location, ())) => transaction_location,
// We're finished with the final address in the column family
None => break,
};

// We found the next address, so we're finished with this address
if transaction_location.address_location() != address_location {
break;
}

// We're past the end height, so we're finished with this query
if transaction_location.transaction_location().height > *query_height_range.end() {
break;
}
let transaction_location_range =
AddressTransaction::address_iterator_range(address_location, query_height_range);

addr_transactions.insert(transaction_location);

// A potentially invalid key representing the next possible output
transaction_location.address_iterator_next();
}

addr_transactions
self.db
.zs_range_iter(&tx_loc_by_transparent_addr_loc, transaction_location_range)
.map(|(tx_loc, ())| tx_loc)
.collect()
}

// Address index queries
Expand Down
Loading