Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 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
6 changes: 6 additions & 0 deletions integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ zaino-fetch = { workspace = true }
zaino-testutils = { workspace = true }
tracing.workspace = true

# Pin to a yanked version that `zcash_protocol ^0.7.2` transitively requires
# via `core2 = "^0.3"`. All 0.3.x are yanked, so fresh resolves without this
# explicit pin fail. The root workspace `Cargo.lock` already has 0.3.3 locked.
# TODO: drop when `zcash_protocol` no longer pulls `core2 0.3.x`.
core2 = "=0.3.3"

[dev-dependencies]
anyhow = { workspace = true }

Expand Down
57 changes: 54 additions & 3 deletions integration-tests/tests/fetch_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use futures::StreamExt as _;
use hex::ToHex as _;
use zaino_fetch::jsonrpsee::connector::{test_node_and_return_url, JsonRpSeeConnector};
use zaino_fetch::jsonrpsee::request::block_selector::BlockSelector;
use zaino_proto::proto::compact_formats::CompactBlock;
use zaino_proto::proto::service::{
AddressList, BlockId, BlockRange, GetAddressUtxosArg, GetMempoolTxRequest, GetSubtreeRootsArg,
Expand All @@ -13,11 +14,12 @@ use zaino_state::FetchServiceSubscriber;
#[allow(deprecated)]
use zaino_state::{FetchService, LightWalletIndexer, Status, StatusType, ZcashIndexer};
use zaino_testutils::{TestManager, ValidatorExt, ValidatorKind};
use zebra_chain::block::Height;
use zebra_chain::parameters::subsidy::ParameterSubsidy as _;
use zebra_chain::subtree::NoteCommitmentSubtreeIndex;
use zebra_rpc::client::ValidateAddressResponse;
use zebra_rpc::methods::{
GetAddressBalanceRequest, GetAddressTxIdsRequest, GetBlock, GetBlockHash,
GetAddressBalanceRequest, GetAddressTxIdsRequest, GetBlock, GetBlockHashResponse,
};
use zip32::AccountId;

Expand Down Expand Up @@ -922,17 +924,56 @@ async fn fetch_service_get_best_blockhash<V: ValidatorExt>(validator: &Validator
_ => None,
};

let fetch_service_get_best_blockhash: GetBlockHash =
let fetch_service_get_best_blockhash: GetBlockHashResponse =
dbg!(fetch_service_subscriber.get_best_blockhash().await.unwrap());

assert_eq!(
fetch_service_get_best_blockhash.hash(),
ret.expect("ret to be Some(GetBlockHash) not None")
ret.expect("ret to be Some(GetBlockHashResponse) not None")
);

test_manager.close().await;
}

#[allow(deprecated)]
async fn fetch_service_get_blockhash<V: ValidatorExt>(validator: &ValidatorKind) {
let mut test_manager =
TestManager::<V, FetchService>::launch(validator, None, None, None, true, false, false)
.await
.unwrap();
let fetch_service_subscriber = test_manager.service_subscriber.take().unwrap();

test_manager
.generate_blocks_and_poll_indexer(5, &fetch_service_subscriber)
.await;

// Resolve the expected hash for a fixed, non-tip height via z_get_block.
let inspected_block: GetBlock = fetch_service_subscriber
.z_get_block("7".to_string(), Some(1))
.await
.unwrap();
let expected_hash_at_height = match inspected_block {
GetBlock::Object(obj) => obj.hash(),
GetBlock::Raw(_) => panic!("expected Object variant with verbosity=1"),
};

let got_by_height: GetBlockHashResponse = fetch_service_subscriber
.get_blockhash(BlockSelector::Height(Height(7)))
.await
.unwrap();
assert_eq!(got_by_height.hash(), expected_hash_at_height);

// Tip selector should agree with get_best_blockhash.
let got_by_tip: GetBlockHashResponse = fetch_service_subscriber
.get_blockhash(BlockSelector::Tip)
.await
.unwrap();
let best = fetch_service_subscriber.get_best_blockhash().await.unwrap();
assert_eq!(got_by_tip.hash(), best.hash());

test_manager.close().await;
}

#[allow(deprecated)]
async fn fetch_service_get_block_count<V: ValidatorExt>(validator: &ValidatorKind) {
let mut test_manager =
Expand Down Expand Up @@ -2301,6 +2342,11 @@ mod zcashd {
fetch_service_get_best_blockhash::<Zcashd>(&ValidatorKind::Zcashd).await;
}

#[tokio::test(flavor = "multi_thread")]
pub(crate) async fn blockhash() {
fetch_service_get_blockhash::<Zcashd>(&ValidatorKind::Zcashd).await;
}

#[tokio::test(flavor = "multi_thread")]
pub(crate) async fn block_count() {
fetch_service_get_block_count::<Zcashd>(&ValidatorKind::Zcashd).await;
Expand Down Expand Up @@ -2570,6 +2616,11 @@ mod zebrad {
fetch_service_get_best_blockhash::<Zebrad>(&ValidatorKind::Zebrad).await;
}

#[tokio::test(flavor = "multi_thread")]
pub(crate) async fn blockhash() {
fetch_service_get_blockhash::<Zebrad>(&ValidatorKind::Zebrad).await;
}

#[tokio::test(flavor = "multi_thread")]
pub(crate) async fn block_count() {
fetch_service_get_block_count::<Zebrad>(&ValidatorKind::Zebrad).await;
Expand Down
96 changes: 95 additions & 1 deletion integration-tests/tests/state_service.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use futures::StreamExt;
use zaino_common::network::ActivationHeights;
use zaino_common::{DatabaseConfig, ServiceConfig, StorageConfig};
use zaino_fetch::jsonrpsee::request::block_selector::BlockSelector;
use zaino_fetch::jsonrpsee::response::address_deltas::GetAddressDeltasParams;
use zaino_proto::proto::service::{BlockId, BlockRange, PoolType, TransparentAddressBlockFilter};
use zaino_state::ChainIndex as _;
Expand All @@ -16,9 +17,12 @@ use zaino_testutils::{TestManager, ValidatorKind, ZEBRAD_TESTNET_CACHE_DIR};
use zainodlib::config::ZainodConfig;
use zainodlib::error::IndexerError;
use zcash_local_net::validator::{zebrad::Zebrad, Validator};
use zebra_chain::block::Height;
use zebra_chain::parameters::NetworkKind;
use zebra_chain::subtree::NoteCommitmentSubtreeIndex;
use zebra_rpc::methods::{GetAddressBalanceRequest, GetAddressTxIdsRequest, GetInfo};
use zebra_rpc::methods::{
GetAddressBalanceRequest, GetAddressTxIdsRequest, GetBlockHashResponse, GetInfo,
};
use zip32::AccountId;

#[allow(deprecated)]
Expand Down Expand Up @@ -533,6 +537,86 @@ async fn state_service_get_block_object(
test_manager.close().await;
}

async fn state_service_get_blockhash(
validator: &ValidatorKind,
chain_cache: Option<std::path::PathBuf>,
network: NetworkKind,
) {
let (
mut test_manager,
_fetch_service,
fetch_service_subscriber,
_state_service,
state_service_subscriber,
) = create_test_manager_and_services::<Zebrad>(
validator,
chain_cache,
false,
false,
Some(network),
)
.await;

let target_height: u32 = match network {
NetworkKind::Regtest => 1,
_ => 1_000_000,
};

// Reference hash: pull block at target_height via z_get_block.
let reference_block = fetch_service_subscriber
.z_get_block(target_height.to_string(), Some(1))
.await
.unwrap();
let expected_hash_at_height = match reference_block {
zebra_rpc::methods::GetBlock::Object(obj) => obj.hash(),
zebra_rpc::methods::GetBlock::Raw(_) => panic!("expected Object variant with verbosity=1"),
};

let got_by_height: GetBlockHashResponse = state_service_subscriber
.get_blockhash(BlockSelector::Height(Height(target_height)))
.await
.unwrap();
assert_eq!(got_by_height.hash(), expected_hash_at_height);

// Tip selector should agree with get_best_blockhash.
let got_by_tip: GetBlockHashResponse = state_service_subscriber
.get_blockhash(BlockSelector::Tip)
.await
.unwrap();
let best = state_service_subscriber.get_best_blockhash().await.unwrap();
assert_eq!(got_by_tip.hash(), best.hash());

test_manager.close().await;
}

async fn state_service_get_blockhash_out_of_range_returns_err(validator: &ValidatorKind) {
let (
mut test_manager,
_fetch_service,
_fetch_service_subscriber,
_state_service,
state_service_subscriber,
) = create_test_manager_and_services::<Zebrad>(
validator,
None,
false,
false,
Some(NetworkKind::Regtest),
)
.await;

// A regtest chain at launch has only a handful of blocks; 9_999 is far beyond any tip.
let far_beyond_tip = BlockSelector::Height(Height(9_999));
let result = state_service_subscriber.get_blockhash(far_beyond_tip).await;

assert!(
result.is_err(),
"out-of-range height must return Err, not panic or succeed"
);

test_manager.close().await;
}

async fn state_service_get_raw_mempool<V: ValidatorExt>(validator: &ValidatorKind) {
let (
mut test_manager,
Expand Down Expand Up @@ -2415,6 +2499,16 @@ mod zebra {
.await;
}

#[tokio::test(flavor = "multi_thread")]
async fn blockhash_regtest() {
state_service_get_blockhash(&ValidatorKind::Zebrad, None, NetworkKind::Regtest).await;
}

#[tokio::test(flavor = "multi_thread")]
async fn blockhash_out_of_range_returns_err() {
state_service_get_blockhash_out_of_range_returns_err(&ValidatorKind::Zebrad).await;
}

#[ignore = "requires fully synced testnet."]
#[tokio::test(flavor = "multi_thread")]
async fn block_object_testnet() {
Expand Down
33 changes: 26 additions & 7 deletions integration-tests/zaino-testutils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,17 +639,36 @@ where
n: u32,
chain_index: &NodeBackedChainIndexSubscriber,
) {
async fn current_tip_height(chain_index: &NodeBackedChainIndexSubscriber) -> u32 {
let snapshot = chain_index.snapshot_nonfinalized_state().await.unwrap();
u32::from(chain_index.best_chaintip(&snapshot).await.unwrap().height)
}

let chain_height = self.local_net.get_chain_height().await;
let mut next_block_height = chain_height + 1;
let mut interval = tokio::time::interval(std::time::Duration::from_millis(200));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
interval.tick().await;
while current_tip_height(chain_index).await < chain_height + n {
// Read the current nonfinalized tip height via the public ChainIndex API.
// `snapshot_nonfinalized_state` became async returning a Result, and the
// snapshot's inner `NonfinalizedBlockCacheSnapshot` is now `pub(crate)`,
// so external callers must go through `best_chaintip(&snapshot)`.
//
// Friction note: this patch was needed because the integration-tests
// workspace is separate from the root workspace, so these call sites
// rotted silently across a dev merge — root-workspace CI never compiled
// them. Expect the same class of breakage to recur in `chain_cache.rs`
// and anywhere else that consumes `ChainIndex::Snapshot` from outside
// `zaino-state`, until the split-workspace issue is addressed.
// See https://github.com/zingolabs/zaino/issues/1043.
async fn read_tip_height(chain_index: &NodeBackedChainIndexSubscriber) -> u32 {
let snapshot = chain_index
.snapshot_nonfinalized_state()
.await
.expect("chain index snapshot should succeed during test setup");
let tip = chain_index
.best_chaintip(&snapshot)
.await
.expect("best chaintip should be available during test setup");
u32::from(tip.height)
}

while read_tip_height(chain_index).await < chain_height + n {
// Check liveness - fail fast if the chain index is dead
if !chain_index.is_live() {
let status = chain_index.combined_status();
Expand All @@ -663,7 +682,7 @@ where
interval.tick().await;
} else {
self.local_net.generate_blocks(1).await.unwrap();
while current_tip_height(chain_index).await != next_block_height {
while read_tip_height(chain_index).await != next_block_height {
if !chain_index.is_live() {
let status = chain_index.combined_status();
panic!(
Expand Down
1 change: 1 addition & 0 deletions packages/zaino-fetch/src/jsonrpsee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

pub mod connector;
pub mod error;
pub mod request;
pub mod response;
23 changes: 19 additions & 4 deletions packages/zaino-fetch/src/jsonrpsee/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use std::{
use tracing::error;
use zebra_rpc::client::ValidateAddressResponse;

use crate::jsonrpsee::request::block_selector::BlockSelector;
use crate::jsonrpsee::response::address_deltas::GetAddressDeltasError;
use crate::jsonrpsee::{
error::{JsonRpcError, TransportError},
Expand All @@ -34,10 +35,10 @@ use crate::jsonrpsee::{
peer_info::GetPeerInfo,
z_validate_address::{ZValidateAddressError, ZValidateAddressResponse},
GetBalanceError, GetBalanceResponse, GetBlockCountResponse, GetBlockError, GetBlockHash,
GetBlockResponse, GetBlockchainInfoResponse, GetInfoResponse, GetMempoolInfoResponse,
GetSubtreesError, GetSubtreesResponse, GetTransactionResponse, GetTreestateError,
GetTreestateResponse, GetUtxosError, GetUtxosResponse, SendTransactionError,
SendTransactionResponse, TxidsError, TxidsResponse,
GetBlockHashByIndex, GetBlockHashError, GetBlockResponse, GetBlockchainInfoResponse,
GetInfoResponse, GetMempoolInfoResponse, GetSubtreesError, GetSubtreesResponse,
GetTransactionResponse, GetTreestateError, GetTreestateResponse, GetUtxosError,
GetUtxosResponse, SendTransactionError, SendTransactionResponse, TxidsError, TxidsResponse,
},
};

Expand Down Expand Up @@ -625,6 +626,20 @@ impl JsonRpSeeConnector {
.await
}

/// Returns the hash of the block at the given index in the best valid block chain.
///
/// zcashd reference: [`getblockhash`](https://zcash.github.io/rpc/getblockhash.html)
/// method: post
/// tags: blockchain
pub async fn get_blockhash(
&self,
block_index: BlockSelector,
) -> Result<GetBlockHash, RpcRequestError<GetBlockHashError>> {
let params = [serde_json::to_value(block_index).map_err(RpcRequestError::JsonRpc)?];
let wrapped: GetBlockHashByIndex = self.send_request("getblockhash", params).await?;
Ok(wrapped.0)
}

/// Returns the height of the most recent block in the best valid block chain
/// (equivalently, the number of blocks in this chain excluding the genesis block).
///
Expand Down
7 changes: 7 additions & 0 deletions packages/zaino-fetch/src/jsonrpsee/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Request parameter types for jsonRPSeeConnector.
//!
//! These types model the inputs accepted by Zebra's JSON-RPC endpoints, kept
//! separate from [`crate::jsonrpsee::response`] so request and response
//! shapes do not end up colocated under a single module name.

pub mod block_selector;
Loading
Loading