Merge pull request #77 from hl-archive-node/feat/cutoff-latest

feat: Add debug CLI flag to enforce latest blocks (--debug-cutoff-height)
This commit is contained in:
sprites0
2025-10-02 10:57:04 -04:00
committed by GitHub
7 changed files with 51 additions and 8 deletions

View File

@ -40,7 +40,8 @@ fn main() -> eyre::Result<()> {
ext: HlNodeArgs| async move { ext: HlNodeArgs| async move {
let default_upstream_rpc_url = builder.config().chain.official_rpc_url(); let default_upstream_rpc_url = builder.config().chain.official_rpc_url();
let (node, engine_handle_tx) = HlNode::new(ext.block_source_args.parse().await?); let (node, engine_handle_tx) =
HlNode::new(ext.block_source_args.parse().await?, ext.debug_cutoff_height);
let NodeHandle { node, node_exit_future: exit_future } = builder let NodeHandle { node, node_exit_future: exit_future } = builder
.node(node) .node(node)
.extend_rpc_modules(move |mut ctx| { .extend_rpc_modules(move |mut ctx| {

View File

@ -35,6 +35,12 @@ pub struct HlNodeArgs {
#[command(flatten)] #[command(flatten)]
pub block_source_args: BlockSourceArgs, pub block_source_args: BlockSourceArgs,
/// Debug cutoff height.
///
/// This option is used to cut off the block import at a specific height.
#[arg(long, env = "DEBUG_CUTOFF_HEIGHT")]
pub debug_cutoff_height: Option<u64>,
/// Upstream RPC URL to forward incoming transactions. /// Upstream RPC URL to forward incoming transactions.
/// ///
/// Default to Hyperliquid's RPC URL when not provided (https://rpc.hyperliquid.xyz/evm). /// Default to Hyperliquid's RPC URL when not provided (https://rpc.hyperliquid.xyz/evm).

View File

@ -49,14 +49,23 @@ pub type HlNodeAddOns<N> =
pub struct HlNode { pub struct HlNode {
engine_handle_rx: Arc<Mutex<Option<oneshot::Receiver<ConsensusEngineHandle<HlPayloadTypes>>>>>, engine_handle_rx: Arc<Mutex<Option<oneshot::Receiver<ConsensusEngineHandle<HlPayloadTypes>>>>>,
block_source_config: BlockSourceConfig, block_source_config: BlockSourceConfig,
debug_cutoff_height: Option<u64>,
} }
impl HlNode { impl HlNode {
pub fn new( pub fn new(
block_source_config: BlockSourceConfig, block_source_config: BlockSourceConfig,
debug_cutoff_height: Option<u64>,
) -> (Self, oneshot::Sender<ConsensusEngineHandle<HlPayloadTypes>>) { ) -> (Self, oneshot::Sender<ConsensusEngineHandle<HlPayloadTypes>>) {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
(Self { engine_handle_rx: Arc::new(Mutex::new(Some(rx))), block_source_config }, tx) (
Self {
engine_handle_rx: Arc::new(Mutex::new(Some(rx))),
block_source_config,
debug_cutoff_height,
},
tx,
)
} }
} }
@ -84,6 +93,7 @@ impl HlNode {
.network(HlNetworkBuilder { .network(HlNetworkBuilder {
engine_handle_rx: self.engine_handle_rx.clone(), engine_handle_rx: self.engine_handle_rx.clone(),
block_source_config: self.block_source_config.clone(), block_source_config: self.block_source_config.clone(),
debug_cutoff_height: self.debug_cutoff_height,
}) })
.consensus(HlConsensusBuilder::default()) .consensus(HlConsensusBuilder::default())
} }

View File

@ -142,6 +142,8 @@ pub struct HlNetworkBuilder {
Arc<Mutex<Option<oneshot::Receiver<ConsensusEngineHandle<HlPayloadTypes>>>>>, Arc<Mutex<Option<oneshot::Receiver<ConsensusEngineHandle<HlPayloadTypes>>>>>,
pub(crate) block_source_config: BlockSourceConfig, pub(crate) block_source_config: BlockSourceConfig,
pub(crate) debug_cutoff_height: Option<u64>,
} }
impl HlNetworkBuilder { impl HlNetworkBuilder {
@ -203,6 +205,7 @@ where
pool: Pool, pool: Pool,
) -> eyre::Result<Self::Network> { ) -> eyre::Result<Self::Network> {
let block_source_config = self.block_source_config.clone(); let block_source_config = self.block_source_config.clone();
let debug_cutoff_height = self.debug_cutoff_height;
let handle = let handle =
ctx.start_network(NetworkManager::builder(self.network_config(ctx)?).await?, pool); ctx.start_network(NetworkManager::builder(self.network_config(ctx)?).await?, pool);
let local_node_record = handle.local_node_record(); let local_node_record = handle.local_node_record();
@ -223,6 +226,7 @@ where
block_source_config block_source_config
.create_cached_block_source((*chain_spec).clone(), next_block_number) .create_cached_block_source((*chain_spec).clone(), next_block_number)
.await, .await,
debug_cutoff_height,
) )
.await .await
.unwrap(); .unwrap();

View File

@ -37,6 +37,7 @@ pub async fn start_pseudo_peer(
chain_spec: Arc<HlChainSpec>, chain_spec: Arc<HlChainSpec>,
destination_peer: String, destination_peer: String,
block_source: BlockSourceBoxed, block_source: BlockSourceBoxed,
debug_cutoff_height: Option<u64>,
) -> eyre::Result<()> { ) -> eyre::Result<()> {
let blockhash_cache = new_blockhash_cache(); let blockhash_cache = new_blockhash_cache();
@ -46,6 +47,7 @@ pub async fn start_pseudo_peer(
destination_peer, destination_peer,
block_source.clone(), block_source.clone(),
blockhash_cache.clone(), blockhash_cache.clone(),
debug_cutoff_height,
) )
.await?; .await?;

View File

@ -20,6 +20,7 @@ pub struct NetworkBuilder {
discovery_port: u16, discovery_port: u16,
listener_port: u16, listener_port: u16,
chain_spec: HlChainSpec, chain_spec: HlChainSpec,
debug_cutoff_height: Option<u64>,
} }
impl Default for NetworkBuilder { impl Default for NetworkBuilder {
@ -31,6 +32,7 @@ impl Default for NetworkBuilder {
discovery_port: 0, discovery_port: 0,
listener_port: 0, listener_port: 0,
chain_spec: HlChainSpec::default(), chain_spec: HlChainSpec::default(),
debug_cutoff_height: None,
} }
} }
} }
@ -46,6 +48,11 @@ impl NetworkBuilder {
self self
} }
pub fn with_debug_cutoff_height(mut self, debug_cutoff_height: Option<u64>) -> Self {
self.debug_cutoff_height = debug_cutoff_height;
self
}
pub async fn build<BS>( pub async fn build<BS>(
self, self,
block_source: Arc<Box<dyn super::sources::BlockSource>>, block_source: Arc<Box<dyn super::sources::BlockSource>>,
@ -58,8 +65,12 @@ impl NetworkBuilder {
.listener_addr(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), self.listener_port)); .listener_addr(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), self.listener_port));
let chain_id = self.chain_spec.inner.chain().id(); let chain_id = self.chain_spec.inner.chain().id();
let (block_poller, start_tx) = let (block_poller, start_tx) = BlockPoller::new_suspended(
BlockPoller::new_suspended(chain_id, block_source, blockhash_cache); chain_id,
block_source,
blockhash_cache,
self.debug_cutoff_height,
);
let config = builder.block_import(Box::new(block_poller)).build(Arc::new(NoopProvider::< let config = builder.block_import(Box::new(block_poller)).build(Arc::new(NoopProvider::<
HlChainSpec, HlChainSpec,
HlPrimitives, HlPrimitives,
@ -77,10 +88,12 @@ pub async fn create_network_manager<BS>(
destination_peer: String, destination_peer: String,
block_source: Arc<Box<dyn super::sources::BlockSource>>, block_source: Arc<Box<dyn super::sources::BlockSource>>,
blockhash_cache: BlockHashCache, blockhash_cache: BlockHashCache,
debug_cutoff_height: Option<u64>,
) -> eyre::Result<(NetworkManager<HlNetworkPrimitives>, mpsc::Sender<()>)> { ) -> eyre::Result<(NetworkManager<HlNetworkPrimitives>, mpsc::Sender<()>)> {
NetworkBuilder::default() NetworkBuilder::default()
.with_boot_nodes(vec![TrustedPeer::from_str(&destination_peer).unwrap()]) .with_boot_nodes(vec![TrustedPeer::from_str(&destination_peer).unwrap()])
.with_chain_spec(chain_spec) .with_chain_spec(chain_spec)
.with_debug_cutoff_height(debug_cutoff_height)
.build::<BS>(block_source, blockhash_cache) .build::<BS>(block_source, blockhash_cache)
.await .await
} }

View File

@ -52,12 +52,12 @@ impl BlockPoller {
chain_id: u64, chain_id: u64,
block_source: BS, block_source: BS,
blockhash_cache: BlockHashCache, blockhash_cache: BlockHashCache,
debug_cutoff_height: Option<u64>,
) -> (Self, mpsc::Sender<()>) { ) -> (Self, mpsc::Sender<()>) {
let block_source = Arc::new(block_source); let block_source = Arc::new(block_source);
let (start_tx, start_rx) = mpsc::channel(1); let (start_tx, start_rx) = mpsc::channel(1);
let (block_tx, block_rx) = mpsc::channel(100); let (block_tx, block_rx) = mpsc::channel(100);
let block_tx_clone = block_tx.clone(); let task = tokio::spawn(Self::task(start_rx, block_source, block_tx, debug_cutoff_height));
let task = tokio::spawn(Self::task(start_rx, block_source, block_tx_clone));
(Self { chain_id, block_rx, task, blockhash_cache: blockhash_cache.clone() }, start_tx) (Self { chain_id, block_rx, task, blockhash_cache: blockhash_cache.clone() }, start_tx)
} }
@ -69,7 +69,8 @@ impl BlockPoller {
async fn task<BS: BlockSource>( async fn task<BS: BlockSource>(
mut start_rx: mpsc::Receiver<()>, mut start_rx: mpsc::Receiver<()>,
block_source: Arc<BS>, block_source: Arc<BS>,
block_tx_clone: mpsc::Sender<(u64, BlockAndReceipts)>, block_tx: mpsc::Sender<(u64, BlockAndReceipts)>,
debug_cutoff_height: Option<u64>,
) -> eyre::Result<()> { ) -> eyre::Result<()> {
start_rx.recv().await.ok_or(eyre::eyre!("Failed to receive start signal"))?; start_rx.recv().await.ok_or(eyre::eyre!("Failed to receive start signal"))?;
info!("Starting block poller"); info!("Starting block poller");
@ -80,10 +81,16 @@ impl BlockPoller {
.await .await
.ok_or(eyre::eyre!("Failed to find latest block number"))?; .ok_or(eyre::eyre!("Failed to find latest block number"))?;
if let Some(debug_cutoff_height) = debug_cutoff_height {
if next_block_number > debug_cutoff_height {
next_block_number = debug_cutoff_height;
}
}
loop { loop {
match block_source.collect_block(next_block_number).await { match block_source.collect_block(next_block_number).await {
Ok(block) => { Ok(block) => {
block_tx_clone.send((next_block_number, block)).await?; block_tx.send((next_block_number, block)).await?;
next_block_number += 1; next_block_number += 1;
} }
Err(_) => tokio::time::sleep(polling_interval).await, Err(_) => tokio::time::sleep(polling_interval).await,