change(rpc): return an error from getblocktemplate method if Zebra is not synced to network tip (#5623)
* Returns error from getblocktemplate if not synced * sets max estimated distance to 1 * removes unnecessary calls to best_tip_height adds info log when estimated_distance_to_chain_tip is too high * trigger GitHub actions Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
2f6170ee4a
commit
57fde15e5e
|
@ -58,6 +58,28 @@ pub trait ChainTip {
|
|||
|
||||
Some(estimator.estimate_height_at(now))
|
||||
}
|
||||
|
||||
/// Return an estimate of how many blocks there are ahead of Zebra's best chain tip
|
||||
/// until the network chain tip, and Zebra's best chain tip height.
|
||||
///
|
||||
/// The estimate is calculated based on the current local time, the block time of the best tip
|
||||
/// and the height of the best tip.
|
||||
///
|
||||
/// This estimate may be negative if the current local time is behind the chain tip block's timestamp.
|
||||
fn estimate_distance_to_network_chain_tip(
|
||||
&self,
|
||||
network: Network,
|
||||
) -> Option<(i32, block::Height)> {
|
||||
let (current_height, current_block_time) = self.best_tip_height_and_block_time()?;
|
||||
|
||||
let estimator =
|
||||
NetworkChainTipHeightEstimator::new(current_block_time, current_height, network);
|
||||
|
||||
Some((
|
||||
estimator.estimate_height_at(Utc::now()) - current_height,
|
||||
current_height,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A chain tip that is always empty.
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
|||
use chrono::{DateTime, Utc};
|
||||
use tokio::sync::watch;
|
||||
|
||||
use crate::{block, chain_tip::ChainTip, transaction};
|
||||
use crate::{block, chain_tip::ChainTip, parameters::Network, transaction};
|
||||
|
||||
/// A sender to sets the values read by a [`MockChainTip`].
|
||||
pub struct MockChainTipSender {
|
||||
|
@ -17,6 +17,9 @@ pub struct MockChainTipSender {
|
|||
|
||||
/// A sender that sets the `best_tip_block_time` of a [`MockChainTip`].
|
||||
best_tip_block_time: watch::Sender<Option<DateTime<Utc>>>,
|
||||
|
||||
/// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
|
||||
estimated_distance_to_network_chain_tip: watch::Sender<Option<i32>>,
|
||||
}
|
||||
|
||||
/// A mock [`ChainTip`] implementation that allows setting the `best_tip_height` externally.
|
||||
|
@ -30,6 +33,9 @@ pub struct MockChainTip {
|
|||
|
||||
/// A mocked `best_tip_height` value set by the [`MockChainTipSender`].
|
||||
best_tip_block_time: watch::Receiver<Option<DateTime<Utc>>>,
|
||||
|
||||
/// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
|
||||
estimated_distance_to_network_chain_tip: watch::Receiver<Option<i32>>,
|
||||
}
|
||||
|
||||
impl MockChainTip {
|
||||
|
@ -43,17 +49,21 @@ impl MockChainTip {
|
|||
let (height_sender, height_receiver) = watch::channel(None);
|
||||
let (hash_sender, hash_receiver) = watch::channel(None);
|
||||
let (time_sender, time_receiver) = watch::channel(None);
|
||||
let (estimated_distance_to_tip_sender, estimated_distance_to_tip_receiver) =
|
||||
watch::channel(None);
|
||||
|
||||
let mock_chain_tip = MockChainTip {
|
||||
best_tip_height: height_receiver,
|
||||
best_tip_hash: hash_receiver,
|
||||
best_tip_block_time: time_receiver,
|
||||
estimated_distance_to_network_chain_tip: estimated_distance_to_tip_receiver,
|
||||
};
|
||||
|
||||
let mock_chain_tip_sender = MockChainTipSender {
|
||||
best_tip_height: height_sender,
|
||||
best_tip_hash: hash_sender,
|
||||
best_tip_block_time: time_sender,
|
||||
estimated_distance_to_network_chain_tip: estimated_distance_to_tip_sender,
|
||||
};
|
||||
|
||||
(mock_chain_tip, mock_chain_tip_sender)
|
||||
|
@ -90,6 +100,18 @@ impl ChainTip for MockChainTip {
|
|||
fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
|
||||
unreachable!("Method not used in tests");
|
||||
}
|
||||
|
||||
fn estimate_distance_to_network_chain_tip(
|
||||
&self,
|
||||
_network: Network,
|
||||
) -> Option<(i32, block::Height)> {
|
||||
self.estimated_distance_to_network_chain_tip
|
||||
.borrow()
|
||||
.and_then(|estimated_distance| {
|
||||
self.best_tip_height()
|
||||
.map(|tip_height| (estimated_distance, tip_height))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MockChainTipSender {
|
||||
|
@ -113,4 +135,11 @@ impl MockChainTipSender {
|
|||
.send(block_time.into())
|
||||
.expect("attempt to send a best tip block time to a dropped `MockChainTip`");
|
||||
}
|
||||
|
||||
/// Send a new estimated distance to network chain tip to the [`MockChainTip`].
|
||||
pub fn send_estimated_distance_to_network_chain_tip(&self, distance: impl Into<Option<i32>>) {
|
||||
self.estimated_distance_to_network_chain_tip
|
||||
.send(distance.into())
|
||||
.expect("attempt to send a best tip height to a dropped `MockChainTip`");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,11 @@ pub mod config;
|
|||
pub mod constants;
|
||||
pub(crate) mod types;
|
||||
|
||||
/// The max estimated distance to the chain tip for the getblocktemplate method
|
||||
// Set to 30 in case the local time is a little ahead.
|
||||
// TODO: Replace this with SyncStatus
|
||||
const MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP: i32 = 30;
|
||||
|
||||
/// getblocktemplate RPC method signatures.
|
||||
#[rpc(server)]
|
||||
pub trait GetBlockTemplateRpc {
|
||||
|
@ -281,7 +286,30 @@ where
|
|||
data: None,
|
||||
})?;
|
||||
|
||||
let tip_height = best_chain_tip_height(&latest_chain_tip)?;
|
||||
let (estimated_distance_to_chain_tip, tip_height) = latest_chain_tip
|
||||
.estimate_distance_to_network_chain_tip(network)
|
||||
.ok_or_else(|| Error {
|
||||
code: ErrorCode::ServerError(0),
|
||||
message: "No Chain tip available yet".to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
if estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP {
|
||||
tracing::info!(
|
||||
estimated_distance_to_chain_tip,
|
||||
?tip_height,
|
||||
"Zebra has not synced to the chain tip"
|
||||
);
|
||||
|
||||
return Err(Error {
|
||||
// Return error code -10 (https://github.com/s-nomp/node-stratum-pool/blob/d86ae73f8ff968d9355bb61aac05e0ebef36ccb5/lib/pool.js#L140)
|
||||
// TODO: Confirm that this is the expected error code for !synced
|
||||
code: ErrorCode::ServerError(-10),
|
||||
message: format!("Zebra has not synced to the chain tip, estimated distance: {estimated_distance_to_chain_tip}"),
|
||||
data: None,
|
||||
});
|
||||
}
|
||||
|
||||
let mempool_txs = select_mempool_transactions(mempool).await?;
|
||||
|
||||
let miner_fee = miner_fee(&mempool_txs);
|
||||
|
|
|
@ -61,7 +61,6 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
|||
mempool.clone(),
|
||||
state,
|
||||
read_state.clone(),
|
||||
latest_chain_tip.clone(),
|
||||
settings.clone(),
|
||||
)
|
||||
.await;
|
||||
|
|
|
@ -16,7 +16,6 @@ use zebra_chain::{
|
|||
transparent,
|
||||
};
|
||||
use zebra_node_services::mempool;
|
||||
use zebra_state::LatestChainTip;
|
||||
|
||||
use zebra_test::mock_service::{MockService, PanicAssertion};
|
||||
|
||||
|
@ -38,7 +37,6 @@ pub async fn test_responses<State, ReadState>(
|
|||
>,
|
||||
state: State,
|
||||
read_state: ReadState,
|
||||
_latest_chain_tip: LatestChainTip,
|
||||
settings: Settings,
|
||||
) where
|
||||
State: Service<
|
||||
|
@ -79,6 +77,7 @@ pub async fn test_responses<State, ReadState>(
|
|||
|
||||
let (mock_chain_tip, mock_chain_tip_sender) = MockChainTip::new();
|
||||
mock_chain_tip_sender.send_best_tip_height(NetworkUpgrade::Nu5.activation_height(network));
|
||||
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(0));
|
||||
|
||||
let get_block_template_rpc = GetBlockTemplateRpcImpl::new(
|
||||
network,
|
||||
|
|
|
@ -827,6 +827,7 @@ async fn rpc_getblocktemplate() {
|
|||
|
||||
let (mock_chain_tip, mock_chain_tip_sender) = MockChainTip::new();
|
||||
mock_chain_tip_sender.send_best_tip_height(NetworkUpgrade::Nu5.activation_height(Mainnet));
|
||||
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(0));
|
||||
|
||||
// Init RPC
|
||||
let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new(
|
||||
|
@ -887,6 +888,17 @@ async fn rpc_getblocktemplate() {
|
|||
);
|
||||
|
||||
mempool.expect_no_requests().await;
|
||||
|
||||
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(100));
|
||||
let get_block_template_sync_error = get_block_template_rpc
|
||||
.get_block_template()
|
||||
.await
|
||||
.expect_err("needs an error when estimated distance to network chain tip is far");
|
||||
|
||||
assert_eq!(
|
||||
get_block_template_sync_error.code,
|
||||
ErrorCode::ServerError(-10)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||
|
|
Loading…
Reference in New Issue