diff --git a/zebra-chain/src/chain_tip.rs b/zebra-chain/src/chain_tip.rs
index 923302acd..27fa08e13 100644
--- a/zebra-chain/src/chain_tip.rs
+++ b/zebra-chain/src/chain_tip.rs
@@ -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.
diff --git a/zebra-chain/src/chain_tip/mock.rs b/zebra-chain/src/chain_tip/mock.rs
index b13231d60..fec056a37 100644
--- a/zebra-chain/src/chain_tip/mock.rs
+++ b/zebra-chain/src/chain_tip/mock.rs
@@ -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>>,
+
+ /// A sender that sets the `estimate_distance_to_network_chain_tip` of a [`MockChainTip`].
+ estimated_distance_to_network_chain_tip: watch::Sender >,
}
/// 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 >>,
+
+ /// A mocked `estimate_distance_to_network_chain_tip` value set by the [`MockChainTipSender`].
+ estimated_distance_to_network_chain_tip: watch::Receiver >,
}
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 >) {
+ self.estimated_distance_to_network_chain_tip
+ .send(distance.into())
+ .expect("attempt to send a best tip height to a dropped `MockChainTip`");
+ }
}
diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs
index eda2cbc30..9b8a1bbb0 100644
--- a/zebra-rpc/src/methods/get_block_template_rpcs.rs
+++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs
@@ -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);
diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs
index c7cb6fcd6..ca333a909 100644
--- a/zebra-rpc/src/methods/tests/snapshot.rs
+++ b/zebra-rpc/src/methods/tests/snapshot.rs
@@ -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;
diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
index a97557d60..1b54844cb 100644
--- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
+++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
@@ -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: State,
read_state: ReadState,
- _latest_chain_tip: LatestChainTip,
settings: Settings,
) where
State: Service<
@@ -79,6 +77,7 @@ pub async fn test_responses(
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,
diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs
index c8967141d..b99fa5881 100644
--- a/zebra-rpc/src/methods/tests/vectors.rs
+++ b/zebra-rpc/src/methods/tests/vectors.rs
@@ -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")]