change(rpc): Add value pool balances to `getblockchaininfo` RPC method response (#8769)
* Moves `Zec` type out from behind feature flag * Adds 'ValuePoolBalance` type * Updates getblockchaininfo return type to a BoxFuture * minor refactor * Adds service request * Adds real value balances to getblockchaininfo RPC response * Updates snapshots and the suggested command for updating snapshots * Uses generic error constructors wherever possible and removes outdated TODOs * Updates prop tests to handle mock service requests
This commit is contained in:
parent
11b0833374
commit
83c6725e84
|
@ -14,7 +14,7 @@ pub mod mock;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use network_chain_tip_height_estimator::NetworkChainTipHeightEstimator;
|
||||
pub use network_chain_tip_height_estimator::NetworkChainTipHeightEstimator;
|
||||
|
||||
/// An interface for querying the chain tip.
|
||||
///
|
||||
|
|
|
@ -21,7 +21,7 @@ use tracing::Instrument;
|
|||
use zcash_primitives::consensus::Parameters;
|
||||
use zebra_chain::{
|
||||
block::{self, Height, SerializedBlock},
|
||||
chain_tip::ChainTip,
|
||||
chain_tip::{ChainTip, NetworkChainTipHeightEstimator},
|
||||
parameters::{ConsensusBranchId, Network, NetworkUpgrade},
|
||||
serialization::ZcashDeserialize,
|
||||
subtree::NoteCommitmentSubtreeIndex,
|
||||
|
@ -45,6 +45,8 @@ use errors::{MapServerError, OkOrServerError};
|
|||
// We don't use a types/ module here, because it is redundant.
|
||||
pub mod trees;
|
||||
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
pub mod get_block_template_rpcs;
|
||||
|
||||
|
@ -85,7 +87,7 @@ pub trait Rpc {
|
|||
/// Some fields from the zcashd reference are missing from Zebra's [`GetBlockChainInfo`]. It only contains the fields
|
||||
/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L72-L89)
|
||||
#[rpc(name = "getblockchaininfo")]
|
||||
fn get_blockchain_info(&self) -> Result<GetBlockChainInfo>;
|
||||
fn get_blockchain_info(&self) -> BoxFuture<Result<GetBlockChainInfo>>;
|
||||
|
||||
/// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance.
|
||||
///
|
||||
|
@ -500,43 +502,64 @@ where
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
fn get_blockchain_info(&self) -> Result<GetBlockChainInfo> {
|
||||
let network = &self.network;
|
||||
fn get_blockchain_info(&self) -> BoxFuture<Result<GetBlockChainInfo>> {
|
||||
let network = self.network.clone();
|
||||
let debug_force_finished_sync = self.debug_force_finished_sync;
|
||||
let mut state = self.state.clone();
|
||||
|
||||
async move {
|
||||
// `chain` field
|
||||
let chain = self.network.bip70_network_name();
|
||||
let chain = network.bip70_network_name();
|
||||
|
||||
// `blocks` and `best_block_hash` fields
|
||||
let (tip_height, tip_hash) = self
|
||||
.latest_chain_tip
|
||||
.best_tip_height_and_hash()
|
||||
.ok_or_server_error("No Chain tip available yet")?;
|
||||
let request = zebra_state::ReadRequest::TipPoolValues;
|
||||
let response: zebra_state::ReadResponse = state
|
||||
.ready()
|
||||
.and_then(|service| service.call(request))
|
||||
.await
|
||||
.map_server_error()?;
|
||||
|
||||
// `estimated_height` field
|
||||
let current_block_time = self
|
||||
.latest_chain_tip
|
||||
.best_tip_block_time()
|
||||
.ok_or_server_error("No Chain tip available yet")?;
|
||||
let zebra_state::ReadResponse::TipPoolValues {
|
||||
tip_height,
|
||||
tip_hash,
|
||||
value_balance,
|
||||
} = response
|
||||
else {
|
||||
unreachable!("unmatched response to a TipPoolValues request")
|
||||
};
|
||||
|
||||
let zebra_estimated_height = self
|
||||
.latest_chain_tip
|
||||
.estimate_network_chain_tip_height(network, Utc::now())
|
||||
.ok_or_server_error("No Chain tip available yet")?;
|
||||
let request = zebra_state::ReadRequest::BlockHeader(tip_hash.into());
|
||||
let response: zebra_state::ReadResponse = state
|
||||
.ready()
|
||||
.and_then(|service| service.call(request))
|
||||
.await
|
||||
.map_server_error()?;
|
||||
|
||||
let mut estimated_height =
|
||||
if current_block_time > Utc::now() || zebra_estimated_height < tip_height {
|
||||
let zebra_state::ReadResponse::BlockHeader(block_header) = response else {
|
||||
unreachable!("unmatched response to a BlockHeader request")
|
||||
};
|
||||
|
||||
let tip_block_time = block_header
|
||||
.ok_or_server_error("unexpectedly could not read best chain tip block header")?
|
||||
.time;
|
||||
|
||||
let now = Utc::now();
|
||||
let zebra_estimated_height =
|
||||
NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, &network)
|
||||
.estimate_height_at(now);
|
||||
|
||||
// If we're testing the mempool, force the estimated height to be the actual tip height, otherwise,
|
||||
// check if the estimated height is below Zebra's latest tip height, or if the latest tip's block time is
|
||||
// later than the current time on the local clock.
|
||||
let estimated_height = if tip_block_time > now
|
||||
|| zebra_estimated_height < tip_height
|
||||
|| debug_force_finished_sync
|
||||
{
|
||||
tip_height
|
||||
} else {
|
||||
zebra_estimated_height
|
||||
};
|
||||
|
||||
// If we're testing the mempool, force the estimated height to be the actual tip height.
|
||||
if self.debug_force_finished_sync {
|
||||
estimated_height = tip_height;
|
||||
}
|
||||
|
||||
// `upgrades` object
|
||||
//
|
||||
// Get the network upgrades in height order, like `zcashd`.
|
||||
|
@ -568,12 +591,12 @@ where
|
|||
(tip_height + 1).expect("valid chain tips are a lot less than Height::MAX");
|
||||
let consensus = TipConsensusBranch {
|
||||
chain_tip: ConsensusBranchIdHex(
|
||||
NetworkUpgrade::current(network, tip_height)
|
||||
NetworkUpgrade::current(&network, tip_height)
|
||||
.branch_id()
|
||||
.unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
|
||||
),
|
||||
next_block: ConsensusBranchIdHex(
|
||||
NetworkUpgrade::current(network, next_block_height)
|
||||
NetworkUpgrade::current(&network, next_block_height)
|
||||
.branch_id()
|
||||
.unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
|
||||
),
|
||||
|
@ -584,14 +607,15 @@ where
|
|||
blocks: tip_height,
|
||||
best_block_hash: tip_hash,
|
||||
estimated_height,
|
||||
value_pools: types::ValuePoolBalance::from_value_balance(value_balance),
|
||||
upgrades,
|
||||
consensus,
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
.boxed()
|
||||
}
|
||||
fn get_address_balance(
|
||||
&self,
|
||||
address_strings: AddressStrings,
|
||||
|
@ -615,7 +639,6 @@ where
|
|||
}
|
||||
|
||||
// TODO: use HexData or GetRawTransaction::Bytes to handle the transaction data argument
|
||||
// use a generic error constructor (#5548)
|
||||
fn send_raw_transaction(
|
||||
&self,
|
||||
raw_transaction_hex: String,
|
||||
|
@ -963,7 +986,6 @@ where
|
|||
}
|
||||
|
||||
// TODO: use HexData or SentTransactionHash to handle the transaction ID
|
||||
// use a generic error constructor (#5548)
|
||||
fn get_raw_transaction(
|
||||
&self,
|
||||
txid_hex: String,
|
||||
|
@ -1197,7 +1219,6 @@ where
|
|||
.boxed()
|
||||
}
|
||||
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
fn get_address_tx_ids(
|
||||
&self,
|
||||
request: GetAddressTxIdsRequest,
|
||||
|
@ -1258,7 +1279,6 @@ where
|
|||
.boxed()
|
||||
}
|
||||
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
fn get_address_utxos(
|
||||
&self,
|
||||
address_strings: AddressStrings,
|
||||
|
@ -1372,6 +1392,10 @@ pub struct GetBlockChainInfo {
|
|||
#[serde(rename = "estimatedheight")]
|
||||
estimated_height: Height,
|
||||
|
||||
/// Value pool balances
|
||||
#[serde(rename = "valuePools")]
|
||||
value_pools: [types::ValuePoolBalance; 5],
|
||||
|
||||
/// Status of network upgrades
|
||||
upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
|
||||
|
||||
|
@ -1386,6 +1410,7 @@ impl Default for GetBlockChainInfo {
|
|||
blocks: Height(1),
|
||||
best_block_hash: block::Hash([0; 32]),
|
||||
estimated_height: Height(1),
|
||||
value_pools: types::ValuePoolBalance::zero_pools(),
|
||||
upgrades: IndexMap::new(),
|
||||
consensus: TipConsensusBranch {
|
||||
chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()),
|
||||
|
|
|
@ -551,7 +551,6 @@ where
|
|||
best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0)
|
||||
}
|
||||
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
fn get_block_hash(&self, index: i32) -> BoxFuture<Result<GetBlockHash>> {
|
||||
let mut state = self.state.clone();
|
||||
let latest_chain_tip = self.latest_chain_tip.clone();
|
||||
|
@ -567,11 +566,7 @@ where
|
|||
.ready()
|
||||
.and_then(|service| service.call(request))
|
||||
.await
|
||||
.map_err(|error| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
.map_server_error()?;
|
||||
|
||||
match response {
|
||||
zebra_state::ReadResponse::BlockHash(Some(hash)) => Ok(GetBlockHash(hash)),
|
||||
|
@ -586,7 +581,6 @@ where
|
|||
.boxed()
|
||||
}
|
||||
|
||||
// TODO: use a generic error constructor (#5548)
|
||||
fn get_block_template(
|
||||
&self,
|
||||
parameters: Option<get_block_template::JsonParameters>,
|
||||
|
@ -830,11 +824,7 @@ where
|
|||
Is Zebra shutting down?"
|
||||
);
|
||||
|
||||
return Err(Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: recv_error.to_string(),
|
||||
data: None,
|
||||
});
|
||||
return Err(recv_error).map_server_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,3 @@ pub mod transaction;
|
|||
pub mod unified_address;
|
||||
pub mod validate_address;
|
||||
pub mod z_validate_address;
|
||||
pub mod zec;
|
||||
|
|
|
@ -6,7 +6,7 @@ use zebra_chain::{
|
|||
transparent,
|
||||
};
|
||||
|
||||
use crate::methods::get_block_template_rpcs::types::zec::Zec;
|
||||
use crate::methods::types::Zec;
|
||||
|
||||
/// A response to a `getblocksubsidy` RPC request
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Randomised property tests for RPC methods.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use futures::{join, FutureExt, TryFutureExt};
|
||||
use hex::ToHex;
|
||||
|
@ -11,7 +11,7 @@ use tower::buffer::Buffer;
|
|||
|
||||
use zebra_chain::{
|
||||
amount::{Amount, NonNegative},
|
||||
block::{Block, Height},
|
||||
block::{self, Block, Height},
|
||||
chain_tip::{mock::MockChainTip, NoChainTip},
|
||||
parameters::{
|
||||
Network::{self, *},
|
||||
|
@ -20,6 +20,7 @@ use zebra_chain::{
|
|||
serialization::{ZcashDeserialize, ZcashSerialize},
|
||||
transaction::{self, Transaction, UnminedTx, VerifiedUnminedTx},
|
||||
transparent,
|
||||
value_balance::ValueBalance,
|
||||
};
|
||||
use zebra_node_services::mempool;
|
||||
use zebra_state::BoxError;
|
||||
|
@ -553,19 +554,34 @@ proptest! {
|
|||
NoChainTip,
|
||||
);
|
||||
|
||||
let response = rpc.get_blockchain_info();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let response_fut = rpc.get_blockchain_info();
|
||||
let mock_state_handler = {
|
||||
let mut state = state.clone();
|
||||
async move {
|
||||
state
|
||||
.expect_request(zebra_state::ReadRequest::TipPoolValues)
|
||||
.await
|
||||
.expect("getblockchaininfo should call mock state service with correct request")
|
||||
.respond(Err(BoxError::from("no chain tip available yet")));
|
||||
}
|
||||
};
|
||||
|
||||
let (response, _) = tokio::join!(response_fut, mock_state_handler);
|
||||
|
||||
prop_assert_eq!(
|
||||
&response.err().unwrap().message,
|
||||
"No Chain tip available yet"
|
||||
"no chain tip available yet"
|
||||
);
|
||||
|
||||
// The queue task should continue without errors or panics
|
||||
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
||||
prop_assert!(rpc_tx_queue_task_result.is_none());
|
||||
|
||||
runtime.block_on(async move {
|
||||
mempool.expect_no_requests().await?;
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
Ok::<_, TestCaseError>(())
|
||||
})?;
|
||||
}
|
||||
|
@ -581,17 +597,11 @@ proptest! {
|
|||
let mut mempool = MockService::build().for_prop_tests();
|
||||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_prop_tests();
|
||||
|
||||
// get block data
|
||||
// get arbitrary chain tip data
|
||||
let block_height = block.coinbase_height().unwrap();
|
||||
let block_hash = block.hash();
|
||||
let block_time = block.header.time;
|
||||
|
||||
// create a mocked `ChainTip`
|
||||
let (chain_tip, mock_chain_tip_sender) = MockChainTip::new();
|
||||
mock_chain_tip_sender.send_best_tip_height(block_height);
|
||||
mock_chain_tip_sender.send_best_tip_hash(block_hash);
|
||||
mock_chain_tip_sender.send_best_tip_block_time(block_time);
|
||||
|
||||
// Start RPC with the mocked `ChainTip`
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
|
@ -601,9 +611,43 @@ proptest! {
|
|||
true,
|
||||
mempool.clone(),
|
||||
Buffer::new(state.clone(), 1),
|
||||
chain_tip,
|
||||
NoChainTip,
|
||||
);
|
||||
let response = rpc.get_blockchain_info();
|
||||
|
||||
// check no requests were made during this test
|
||||
runtime.block_on(async move {
|
||||
let response_fut = rpc.get_blockchain_info();
|
||||
let mock_state_handler = {
|
||||
let mut state = state.clone();
|
||||
async move {
|
||||
state
|
||||
.expect_request(zebra_state::ReadRequest::TipPoolValues)
|
||||
.await
|
||||
.expect("getblockchaininfo should call mock state service with correct request")
|
||||
.respond(zebra_state::ReadResponse::TipPoolValues {
|
||||
tip_height: block_height,
|
||||
tip_hash: block_hash,
|
||||
value_balance: ValueBalance::default(),
|
||||
});
|
||||
|
||||
state
|
||||
.expect_request(zebra_state::ReadRequest::BlockHeader(block_hash.into()))
|
||||
.await
|
||||
.expect("getblockchaininfo should call mock state service with correct request")
|
||||
.respond(zebra_state::ReadResponse::BlockHeader(Some(Arc::new(block::Header {
|
||||
time: block_time,
|
||||
version: Default::default(),
|
||||
previous_block_hash: Default::default(),
|
||||
merkle_root: Default::default(),
|
||||
commitment_bytes: Default::default(),
|
||||
difficulty_threshold: Default::default(),
|
||||
nonce: Default::default(),
|
||||
solution: Default::default()
|
||||
}))));
|
||||
}
|
||||
};
|
||||
|
||||
let (response, _) = tokio::join!(response_fut, mock_state_handler);
|
||||
|
||||
// Check response
|
||||
match response {
|
||||
|
@ -643,8 +687,6 @@ proptest! {
|
|||
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
|
||||
prop_assert!(rpc_tx_queue_task_result.is_none());
|
||||
|
||||
// check no requests were made during this test
|
||||
runtime.block_on(async move {
|
||||
mempool.expect_no_requests().await?;
|
||||
state.expect_no_requests().await?;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! To update these snapshots, run:
|
||||
//! ```sh
|
||||
//! cargo insta test --review -p zebra-rpc --lib -- test_rpc_response_data
|
||||
//! cargo insta test --review --release -p zebra-rpc --lib -- test_rpc_response_data
|
||||
//! ```
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
@ -210,6 +210,7 @@ async fn test_rpc_response_data_for_network(network: &Network) {
|
|||
if network.is_a_test_network() && !network.is_default_testnet() {
|
||||
let get_blockchain_info = rpc
|
||||
.get_blockchain_info()
|
||||
.await
|
||||
.expect("We should have a GetBlockChainInfo struct");
|
||||
snapshot_rpc_getblockchaininfo("_future_nu6_height", get_blockchain_info, &settings);
|
||||
|
||||
|
@ -223,6 +224,7 @@ async fn test_rpc_response_data_for_network(network: &Network) {
|
|||
// `getblockchaininfo`
|
||||
let get_blockchain_info = rpc
|
||||
.get_blockchain_info()
|
||||
.await
|
||||
.expect("We should have a GetBlockChainInfo struct");
|
||||
snapshot_rpc_getblockchaininfo("", get_blockchain_info, &settings);
|
||||
|
||||
|
|
|
@ -7,6 +7,33 @@ expression: info
|
|||
"blocks": 10,
|
||||
"bestblockhash": "00074c46a4aa8172df8ae2ad1848a2e084e1b6989b7d9e6132adc938bf835b36",
|
||||
"estimatedheight": "[Height]",
|
||||
"valuePools": [
|
||||
{
|
||||
"id": "transparent",
|
||||
"chainValue": 0.034375,
|
||||
"chainValueZat": 3437500
|
||||
},
|
||||
{
|
||||
"id": "sprout",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "sapling",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "orchard",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "deferred",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
}
|
||||
],
|
||||
"upgrades": {
|
||||
"5ba81b19": {
|
||||
"name": "Overwinter",
|
||||
|
|
|
@ -7,6 +7,33 @@ expression: info
|
|||
"blocks": 10,
|
||||
"bestblockhash": "079f4c752729be63e6341ee9bce42fbbe37236aba22e3deb82405f3c2805c112",
|
||||
"estimatedheight": "[Height]",
|
||||
"valuePools": [
|
||||
{
|
||||
"id": "transparent",
|
||||
"chainValue": 0.034375,
|
||||
"chainValueZat": 3437500
|
||||
},
|
||||
{
|
||||
"id": "sprout",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "sapling",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "orchard",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "deferred",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
}
|
||||
],
|
||||
"upgrades": {
|
||||
"5ba81b19": {
|
||||
"name": "Overwinter",
|
||||
|
|
|
@ -7,6 +7,33 @@ expression: info
|
|||
"blocks": 10,
|
||||
"bestblockhash": "079f4c752729be63e6341ee9bce42fbbe37236aba22e3deb82405f3c2805c112",
|
||||
"estimatedheight": "[Height]",
|
||||
"valuePools": [
|
||||
{
|
||||
"id": "transparent",
|
||||
"chainValue": 0.034375,
|
||||
"chainValueZat": 3437500
|
||||
},
|
||||
{
|
||||
"id": "sprout",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "sapling",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "orchard",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
},
|
||||
{
|
||||
"id": "deferred",
|
||||
"chainValue": 0.0,
|
||||
"chainValueZat": 0
|
||||
}
|
||||
],
|
||||
"upgrades": {
|
||||
"5ba81b19": {
|
||||
"name": "Overwinter",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
//! Types used in RPC methods.
|
||||
|
||||
mod get_blockchain_info;
|
||||
mod zec;
|
||||
|
||||
pub use get_blockchain_info::ValuePoolBalance;
|
||||
pub use zec::Zec;
|
|
@ -0,0 +1,72 @@
|
|||
//! Types used in `getblockchaininfo` RPC method.
|
||||
|
||||
use zebra_chain::{
|
||||
amount::{Amount, NonNegative},
|
||||
value_balance::ValueBalance,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A value pool's balance in Zec and Zatoshis
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ValuePoolBalance {
|
||||
/// Name of the pool
|
||||
id: String,
|
||||
/// Total amount in the pool, in ZEC
|
||||
chain_value: Zec<NonNegative>,
|
||||
/// Total amount in the pool, in zatoshis
|
||||
chain_value_zat: Amount<NonNegative>,
|
||||
}
|
||||
|
||||
impl ValuePoolBalance {
|
||||
/// Returns a list of [`ValuePoolBalance`]s converted from the default [`ValueBalance`].
|
||||
pub fn zero_pools() -> [Self; 5] {
|
||||
Self::from_value_balance(Default::default())
|
||||
}
|
||||
|
||||
/// Creates a new [`ValuePoolBalance`] from a pool name and its value balance.
|
||||
pub fn new(id: impl ToString, amount: Amount<NonNegative>) -> Self {
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
chain_value: Zec::from(amount),
|
||||
chain_value_zat: amount,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`ValuePoolBalance`] for the transparent pool.
|
||||
pub fn transparent(amount: Amount<NonNegative>) -> Self {
|
||||
Self::new("transparent", amount)
|
||||
}
|
||||
|
||||
/// Creates a [`ValuePoolBalance`] for the Sprout pool.
|
||||
pub fn sprout(amount: Amount<NonNegative>) -> Self {
|
||||
Self::new("sprout", amount)
|
||||
}
|
||||
|
||||
/// Creates a [`ValuePoolBalance`] for the Sapling pool.
|
||||
pub fn sapling(amount: Amount<NonNegative>) -> Self {
|
||||
Self::new("sapling", amount)
|
||||
}
|
||||
|
||||
/// Creates a [`ValuePoolBalance`] for the Orchard pool.
|
||||
pub fn orchard(amount: Amount<NonNegative>) -> Self {
|
||||
Self::new("orchard", amount)
|
||||
}
|
||||
|
||||
/// Creates a [`ValuePoolBalance`] for the Deferred pool.
|
||||
pub fn deferred(amount: Amount<NonNegative>) -> Self {
|
||||
Self::new("deferred", amount)
|
||||
}
|
||||
|
||||
/// Converts a [`ValueBalance`] to a list of [`ValuePoolBalance`]s.
|
||||
pub fn from_value_balance(value_balance: ValueBalance<NonNegative>) -> [Self; 5] {
|
||||
[
|
||||
Self::transparent(value_balance.transparent_amount()),
|
||||
Self::sprout(value_balance.sprout_amount()),
|
||||
Self::sapling(value_balance.sapling_amount()),
|
||||
Self::orchard(value_balance.orchard_amount()),
|
||||
Self::deferred(value_balance.deferred_amount()),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -815,6 +815,10 @@ pub enum ReadRequest {
|
|||
/// with the current best chain tip.
|
||||
Tip,
|
||||
|
||||
/// Returns [`ReadResponse::TipPoolValues(Option<(Height, block::Hash, ValueBalance)>)`](ReadResponse::TipPoolValues)
|
||||
/// with the current best chain tip.
|
||||
TipPoolValues,
|
||||
|
||||
/// Computes the depth in the current best chain of the block identified by the given hash.
|
||||
///
|
||||
/// Returns
|
||||
|
@ -1065,6 +1069,7 @@ impl ReadRequest {
|
|||
fn variant_name(&self) -> &'static str {
|
||||
match self {
|
||||
ReadRequest::Tip => "tip",
|
||||
ReadRequest::TipPoolValues => "tip_pool_values",
|
||||
ReadRequest::Depth(_) => "depth",
|
||||
ReadRequest::Block(_) => "block",
|
||||
ReadRequest::BlockHeader(_) => "block_header",
|
||||
|
|
|
@ -10,6 +10,7 @@ use zebra_chain::{
|
|||
subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex},
|
||||
transaction::{self, Transaction},
|
||||
transparent,
|
||||
value_balance::ValueBalance,
|
||||
};
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
|
@ -128,6 +129,17 @@ pub enum ReadResponse {
|
|||
/// Response to [`ReadRequest::Tip`] with the current best chain tip.
|
||||
Tip(Option<(block::Height, block::Hash)>),
|
||||
|
||||
/// Response to [`ReadRequest::TipPoolValues`] with
|
||||
/// the current best chain tip and its [`ValueBalance`].
|
||||
TipPoolValues {
|
||||
/// The current best chain tip height.
|
||||
tip_height: block::Height,
|
||||
/// The current best chain tip hash.
|
||||
tip_hash: block::Hash,
|
||||
/// The value pool balance at the current best chain tip.
|
||||
value_balance: ValueBalance<NonNegative>,
|
||||
},
|
||||
|
||||
/// Response to [`ReadRequest::Depth`] with the depth of the specified block.
|
||||
Depth(Option<u32>),
|
||||
|
||||
|
@ -287,7 +299,8 @@ impl TryFrom<ReadResponse> for Response {
|
|||
|
||||
ReadResponse::ValidBestChainTipNullifiersAndAnchors => Ok(Response::ValidBestChainTipNullifiersAndAnchors),
|
||||
|
||||
ReadResponse::TransactionIdsForBlock(_)
|
||||
ReadResponse::TipPoolValues { .. }
|
||||
| ReadResponse::TransactionIdsForBlock(_)
|
||||
| ReadResponse::SaplingTree(_)
|
||||
| ReadResponse::OrchardTree(_)
|
||||
| ReadResponse::SaplingSubtrees(_)
|
||||
|
|
|
@ -1192,6 +1192,38 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
.wait_for_panics()
|
||||
}
|
||||
|
||||
// Used by `getblockchaininfo` RPC method.
|
||||
ReadRequest::TipPoolValues => {
|
||||
let state = self.clone();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
span.in_scope(move || {
|
||||
let tip_with_value_balance = state
|
||||
.non_finalized_state_receiver
|
||||
.with_watch_data(|non_finalized_state| {
|
||||
read::tip_with_value_balance(
|
||||
non_finalized_state.best_chain(),
|
||||
&state.db,
|
||||
)
|
||||
});
|
||||
|
||||
// The work is done in the future.
|
||||
// TODO: Do this in the Drop impl with the variant name?
|
||||
timer.finish(module_path!(), line!(), "ReadRequest::TipPoolValues");
|
||||
|
||||
let (tip_height, tip_hash, value_balance) = tip_with_value_balance?
|
||||
.ok_or(BoxError::from("no chain tip available yet"))?;
|
||||
|
||||
Ok(ReadResponse::TipPoolValues {
|
||||
tip_height,
|
||||
tip_hash,
|
||||
value_balance,
|
||||
})
|
||||
})
|
||||
})
|
||||
.wait_for_panics()
|
||||
}
|
||||
|
||||
// Used by the StateService.
|
||||
ReadRequest::Depth(hash) => {
|
||||
let state = self.clone();
|
||||
|
|
|
@ -482,6 +482,17 @@ impl Chain {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns the non-finalized tip block height, hash, and total pool value balances.
|
||||
pub fn non_finalized_tip_with_value_balance(
|
||||
&self,
|
||||
) -> (Height, block::Hash, ValueBalance<NonNegative>) {
|
||||
(
|
||||
self.non_finalized_tip_height(),
|
||||
self.non_finalized_tip_hash(),
|
||||
self.chain_value_pools,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the Sprout note commitment tree of the tip of this [`Chain`],
|
||||
/// including all finalized notes, and the non-finalized notes in this chain.
|
||||
///
|
||||
|
|
|
@ -36,7 +36,7 @@ pub use block::{
|
|||
pub use find::{
|
||||
best_tip, block_locator, depth, finalized_state_contains_block_hash, find_chain_hashes,
|
||||
find_chain_headers, hash_by_height, height_by_hash, next_median_time_past,
|
||||
non_finalized_state_contains_block_hash, tip, tip_height,
|
||||
non_finalized_state_contains_block_hash, tip, tip_height, tip_with_value_balance,
|
||||
};
|
||||
pub use tree::{orchard_subtrees, orchard_tree, sapling_subtrees, sapling_tree};
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ use std::{
|
|||
|
||||
use chrono::{DateTime, Utc};
|
||||
use zebra_chain::{
|
||||
amount::NonNegative,
|
||||
block::{self, Block, Height},
|
||||
serialization::DateTime32,
|
||||
value_balance::ValueBalance,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -82,6 +84,40 @@ where
|
|||
tip(chain, db).map(|(_height, hash)| hash)
|
||||
}
|
||||
|
||||
/// Returns the tip of `chain` with its [`ValueBalance`].
|
||||
/// If there is no chain, returns the tip of `db`.
|
||||
pub fn tip_with_value_balance<C>(
|
||||
chain: Option<C>,
|
||||
db: &ZebraDb,
|
||||
) -> Result<Option<(Height, block::Hash, ValueBalance<NonNegative>)>, BoxError>
|
||||
where
|
||||
C: AsRef<Chain>,
|
||||
{
|
||||
match chain.map(|chain| chain.as_ref().non_finalized_tip_with_value_balance()) {
|
||||
tip_with_value_balance @ Some(_) => Ok(tip_with_value_balance),
|
||||
None => {
|
||||
// Retry the finalized state query if it was interrupted by a finalizing block.
|
||||
//
|
||||
// TODO: refactor this into a generic retry(finalized_closure, process_and_check_closure) fn
|
||||
for _ in 0..=FINALIZED_STATE_QUERY_RETRIES {
|
||||
let tip @ Some((tip_height, tip_hash)) = db.tip() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let value_balance = db.finalized_value_pool();
|
||||
|
||||
if tip == db.tip() {
|
||||
return Ok(Some((tip_height, tip_hash, value_balance)));
|
||||
}
|
||||
}
|
||||
|
||||
Err("Zebra is committing too many blocks to the state, \
|
||||
wait until it syncs to the chain tip"
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the depth of block `hash` from the chain tip.
|
||||
/// Searches `chain` for `hash`, then searches `db`.
|
||||
pub fn depth<C>(chain: Option<C>, db: &ZebraDb, hash: block::Hash) -> Option<u32>
|
||||
|
|
Loading…
Reference in New Issue