added alternative block share

This commit is contained in:
Nicholas Wehr
2025-08-11 14:13:55 -07:00
parent aab45b9c02
commit 821846f671
7 changed files with 532 additions and 1 deletions

View File

@ -35,6 +35,7 @@ reth-cli-runner.workspace = true
reth-cli-commands.workspace = true
reth-cli-util.workspace = true
reth-consensus-common.workspace = true
reth-hlfs.workspace = true
reth-rpc-builder.workspace = true
reth-rpc.workspace = true
reth-rpc-types-compat.workspace = true
@ -81,8 +82,9 @@ tracing.workspace = true
serde_json.workspace = true
# async
tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thread"] }
tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thread", "net", "fs"] }
futures.workspace = true
futures-util.workspace = true
# time
time = { workspace = true }

View File

@ -6,6 +6,7 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::ne
mod block_ingest;
mod call_forwarder;
mod serialized;
mod share_blocks;
mod spot_meta;
mod tx_forwarder;
@ -18,6 +19,7 @@ use reth::cli::Cli;
use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
use reth_hyperliquid_types::PrecompilesCache;
use reth_node_ethereum::EthereumNode;
use share_blocks::ShareBlocksArgs;
use tokio::sync::Mutex;
use tracing::info;
use tx_forwarder::EthForwarderApiServer;
@ -40,6 +42,10 @@ struct HyperliquidExtArgs {
/// 3. filters out logs and transactions from subscription.
#[arg(long, default_value = "false")]
pub hl_node_compliant: bool,
/// Enable hlfs to backfill archive blocks
#[command(flatten)]
pub hlfs: ShareBlocksArgs,
}
fn main() {
@ -85,6 +91,14 @@ fn main() {
.launch()
.await?;
// start HLFS (serve + peer-backed backfill) using the node's network
if ext_args.hlfs.share_blocks {
let net = handle.node.network.clone(); // returns a FullNetwork (NetworkHandle under the hood)
// keep handle alive for the whole process
let _hlfs =
share_blocks::ShareBlocks::start_with_network(&ext_args.hlfs, net).await?;
}
let ingest =
BlockIngest { ingest_dir, local_ingest_dir, local_blocks_cache, precompiles_cache };
ingest.run(handle.node).await.unwrap();

View File

@ -0,0 +1,129 @@
use clap::Args;
use once_cell::sync::Lazy;
use reth_hlfs::{Backfiller, Client, Server};
use reth_network_api::{events::NetworkEvent, FullNetwork};
use std::{
collections::HashSet,
net::{IpAddr, SocketAddr},
path::PathBuf,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};
use tokio::{task::JoinHandle, time::timeout};
use tracing::{debug, info, warn};
// use futures_util::StreamExt;
use futures_util::stream::StreamExt;
static RR: Lazy<AtomicUsize> = Lazy::new(|| AtomicUsize::new(0));
#[derive(Args, Clone, Debug)]
pub struct ShareBlocksArgs {
#[arg(long, default_value_t = false)]
pub share_blocks: bool,
#[arg(long, default_value = "0.0.0.0")]
pub share_blocks_host: String,
#[arg(long, default_value_t = 9595)]
pub share_blocks_port: u16,
#[arg(long, default_value = "evm-blocks")]
pub archive_dir: PathBuf,
#[arg(long, default_value_t = 5_000)]
pub hist_threshold: u64,
}
pub struct ShareBlocks {
pub backfiller: Backfiller,
_server: JoinHandle<()>,
_autodetect: JoinHandle<()>,
}
impl ShareBlocks {
pub async fn start_with_network<Net>(args: &ShareBlocksArgs, network: Net) -> eyre::Result<Self>
where
Net: FullNetwork + Clone + 'static,
{
let host: IpAddr = args
.share_blocks_host
.parse()
.map_err(|e| eyre::eyre!("invalid --share-blocks-host: {e}"))?;
let bind: SocketAddr = (host, args.share_blocks_port).into();
let srv = Server::new(bind, &args.archive_dir).with_limits(512, 50);
let _server = tokio::spawn(async move {
if let Err(e) = srv.run().await {
warn!(error=%e, "hlfs: server exited");
}
});
let client = Client::new(Vec::new()).with_timeout(Duration::from_secs(5));
let bf = Backfiller::new(client, &args.archive_dir, args.hist_threshold);
let _autodetect = spawn_autodetect(network, args.share_blocks_port, bf.clone());
info!(%bind, dir=%args.archive_dir.display(), hist_threshold=%args.hist_threshold, "hlfs: enabled (reth peers)");
Ok(Self { backfiller: bf, _server, _autodetect })
}
pub async fn try_fetch_one(&self, block: u64, head: u64) -> eyre::Result<Option<usize>> {
let rr = RR.fetch_add(1, Ordering::Relaxed);
self.backfiller.fetch_if_missing(block, head, rr).await.map_err(|e| eyre::eyre!(e))
// <- fix: HlfsError -> eyre::Report
}
}
fn spawn_autodetect<Net>(network: Net, hlfs_port: u16, backfiller: Backfiller) -> JoinHandle<()>
where
Net: FullNetwork + Clone + 'static,
{
let good: Arc<tokio::sync::Mutex<HashSet<SocketAddr>>> =
Arc::new(tokio::sync::Mutex::new(HashSet::new()));
tokio::spawn(async move {
let mut events = network.event_listener();
loop {
match events.next().await {
Some(NetworkEvent::ActivePeerSession { info, .. }) => {
let addr = SocketAddr::new(info.remote_addr.ip(), hlfs_port);
if probe_hlfs(addr).await {
let mut g = good.lock().await;
if g.insert(addr) {
let v: Vec<_> = g.iter().copied().collect();
backfiller.set_peers(v.clone());
info!(%addr, total=v.len(), "hlfs: peer added");
}
} else {
debug!(%addr, "hlfs: peer has no HLFS");
}
}
Some(_) => {}
None => {
warn!("hlfs: network event stream ended");
break;
}
}
}
})
}
async fn probe_hlfs(addr: SocketAddr) -> bool {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
let res = timeout(Duration::from_secs(2), async {
if let Ok(mut s) = TcpStream::connect(addr).await {
let mut msg = [0u8; 9];
msg[0] = 0x01;
let _ = s.write_all(&msg).await;
let mut op = [0u8; 1];
if s.read_exact(&mut op).await.is_ok() {
return matches!(op[0], 0x02 | 0x03 | 0x04);
}
}
false
})
.await;
matches!(res, Ok(true))
}