nautechsystems/nautilus_trader · error · anyhow::Error
The latest block is empty
Error message
The latest block is empty
What it means
latest_block calls the base tendermint service's GetLatestBlock; `sdk_block` is an optional proto field that can be None if the node returns an empty/unsupported block payload. The library refuses to return an empty block to callers relying on block metadata.
Source
Thrown at crates/adapters/dydx/src/grpc/client.rs:387
let req = GetNodeInfoRequest {};
let info = self.base.get_node_info(req).await?.into_inner();
Ok(info)
}
/// Query for the latest block.
///
/// # Errors
///
/// Returns an error if the query fails.
pub async fn latest_block(&mut self) -> Result<Block, anyhow::Error> {
let req = GetLatestBlockRequest::default();
let latest_block = self
.base
.get_latest_block(req)
.await?
.into_inner()
.sdk_block
.ok_or_else(|| anyhow::anyhow!("The latest block is empty"))?;
Ok(latest_block)
}
/// Query for the latest block height.
///
/// # Errors
///
/// Returns an error if the query fails.
pub async fn latest_block_height(&mut self) -> Result<Height, anyhow::Error> {
let latest_block = self.latest_block().await?;
let header = latest_block
.header
.ok_or_else(|| anyhow::anyhow!("The block doesn't contain a header"))?;
let height = Height(header.height.try_into()?);
Ok(height)
}
/// Query for all perpetual markets.View on GitHub (pinned to 18893faf8b)
Solutions
- Retry after a short delay; a starting node will usually populate sdk_block once synced.
- Switch to a known-good, fully synced gRPC endpoint.
- Check proto crate versions (cometbone/tendermint-proto) match the dydx chain's CometBFT version.
- Fall back to polling latest_block_height from a different endpoint if this one repeatedly returns empty blocks.
Example fix
// before
let block = client.latest_block().await?;
// after: retry transient empty responses
let block = loop {
match client.latest_block().await {
Ok(b) => break b,
Err(e) if e.to_string().contains("latest block is empty") => tokio::time::sleep(Duration::from_millis(500)).await,
Err(e) => return Err(e),
}
}; Defensive patterns
Strategy: retry
Try / catch
for _ in 0..3 {
match client.latest_block().await {
Ok(b) => return Ok(b),
Err(e) if e.to_string().contains("latest block is empty") => tokio::time::sleep(Duration::from_millis(500)).await,
Err(e) => return Err(e),
}
}
Err(anyhow!("latest block empty after retries")) Prevention
- Use low-latency, fully synced RPC endpoints
- Configure retry with backoff around block queries
- Keep comet/tendermint proto crates aligned with the chain release
When it happens
Trigger: Calling latest_block (or latest_block_height) against a node that returns a response without sdk_block — typically a node not fully synced, a proxy/CDN stripping fields, or a proto version mismatch (tendermint vs cometbone field naming).
Common situations: Hitting a load-balanced RPC that routes to a lagging/starting node; version skew between the comet/tendermint proto crate and the node's runtime so `sdk_block` is populated under a different field.
Related errors
- The block doesn't contain a header
- Cannot create sign doc: {e}
- {e}
- Query account request failure, account should exist
- Simulation response does not contain gas info
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/044d62396eb457b5.
Report an issue: GitHub.