nautechsystems/nautilus_trader · error
RPC tick {tick_value} does not match positions: derived gros
Error message
RPC tick {tick_value} does not match positions: derived gross={liquidity_gross} net={liquidity_net}, received gross={} net={} What it means
This error is raised by validate_rpc_snapshot_topology when a tick fetched from the RPC node does not match the liquidity gross/net values that were derived from the position set. It indicates the RPC snapshot and locally derived liquidity state are inconsistent, which would corrupt liquidity-math derived from positions. The library fails closed rather than building a snapshot from contradictory data.
Source
Thrown at crates/adapters/blockchain/src/data/core.rs:2242
}
if derived_ticks.len() != actual_ticks.len() {
anyhow::bail!(
"RPC tick topology does not match positions: derived {} ticks, received {}",
derived_ticks.len(),
actual_ticks.len()
);
}
for (tick_value, (liquidity_gross, liquidity_net)) in derived_ticks {
let actual_tick = actual_ticks
.get(&tick_value)
.with_context(|| format!("RPC snapshot omitted position boundary {tick_value}"))?;
if actual_tick.liquidity_gross != liquidity_gross
|| actual_tick.liquidity_net != liquidity_net
{
anyhow::bail!(
"RPC tick {tick_value} does not match positions: derived gross={liquidity_gross} net={liquidity_net}, received gross={} net={}",
actual_tick.liquidity_gross,
actual_tick.liquidity_net
);
}
}
Ok(())
}
fn timestamp_for_on_chain_snapshot(
profiler: &PoolProfiler,
cached_timestamp: Option<UnixNanos>,
) -> anyhow::Result<UnixNanos> {
cached_timestamp
.or(profiler.last_processed_ts)
.context("missing block timestamp for on-chain snapshot")
}View on GitHub (pinned to 18893faf8b)
Solutions
- Re-fetch the RPC snapshot at the exact block hash/number used for position derivation and retry
- Verify the cache's pool events (mint/burn) are fully synced up to the snapshot block before deriving tick liquidity
- Check for chain reorgs and rebuild the local cache from a canonical block
- Pin a consistent archive RPC endpoint (same node, pinned block) instead of a load-balanced set
- Log both derived and received tick values and compare per-tick to identify which ticks diverge
Example fix
// before: deriving positions and fetching snapshot at different points in time let snapshot = client.fetch_tick_snapshot(pool, latest_block()).await?; validate_rpc_snapshot_topology(&cache, &snapshot)?; // after: pin both to the same block let block = latest_block(); let snapshot = client.fetch_tick_snapshot_at(pool, block).await?; validate_rpc_snapshot_topology(&cache_at_block(&cache, block), &snapshot)?;
Defensive patterns
Strategy: validation
Validate before calling
let actual = rpc_snapshot.ticks.get(&tick_value).ok_or_else(|| anyhow!("tick {tick_value} missing"))?;
if actual.liquidity_gross != derived_gross || actual.liquidity_net != derived_net {
return Err(anyhow!("tick {} mismatch at block {}", tick_value, snapshot_block));
} Type guard
fn ticks_consistent(t: &RpcTick, gross: u128, net: i128) -> bool {
t.liquidity_gross == gross && t.liquidity_net == net
} Prevention
- Pin RPC snapshot fetches to an explicit block number/hash
- Ensure event sync is complete before deriving tick topology
- Use a single consistent archive node rather than load-balanced endpoints
- Handle reorgs by rebuilding the cache from a canonical block
When it happens
Trigger: Calling snapshot/position topology validation (validate_rpc_snapshot_topology) when the tick at tick_value exists in the RPC response but its liquidity_gross or liquidity_net differs from the values computed from cached positions — e.g. the RPC node served a state at a different block, the position derivation used stale events, or the node is behind/reorged.
Common situations: RPC endpoint lagging behind the block used for position derivation; a reorg between the event sync and the snapshot fetch; an archive node with truncated or pruned tick state; concurrent updates to the cache during snapshot validation; misconfigured RPC provider with inconsistent replicas behind a load balancer.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Fetched block {} while requesting RPC snapshot block {}
- Profiler receipt position does not match its ingestion water
- Profiler receipt contains {} logs at global index {}; expect
- Finalized block {} does not contain transaction {}
- Finalized execution transaction {tx_hash} no longer has a re
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/46fc177adc30fd41.
Report an issue: GitHub.