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")]