Repository navigation
Conversation
…evel states A pAMM ladder prices the block that Titan still builds. A client that simulates the swap on mined state simulates a different venue state. The stream now reads a second Titan socket. That socket carries the account state that Titan priced each venue on. The stream joins a snapshot to the ladders of the same venue, pair and pair version, and it attaches the snapshot to the state. Snapshots live in a bounded fail-closed buffer, and a build's serving life is fixed at its first admission. The exported expiry second is now computed once from the build's own deadline. The reconnect and frame-rejected counters gain a socket label, so their previous series go stale on deploy. The module suite grows from 151 to 298 tests.
Reviewers asked what "the wire" is. Every use now says what it means: Titan, the message, the stream, a streamed value, or raw JSON. Test constants and one parameter are renamed to match. No behaviour change.
Captures live pAMM quotes, waits for the block each one targets, and simulates the encoded swap at that block with and without the quote's own overrides. The gate is that the simulated output matches the quoted output; the run without overrides is reported, since whether mined state alone is fresh depends on who built the block. Ignored by default and gated in CI on changes to the price level stream, because it costs a live Titan capture and real RPC calls.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #1597 (
tl/price-level-stream-v2), which it targets, and must merge after it.Summary
mined state therefore simulates a different venue state: the venue reverts
StaleUpdate(), orthe fallback router pays a fallback price.
ladders it belongs to, and hands it to the client on the quote.
Terms used below
and the storage words it overrode, and nothing else. An absent field means the simulation read
the chain's value. The same shape
eth_callaccepts asstateOverrides.calculated_from; venue entries on the new socket repeat it aspairSequences. It is the onlylink between the two sockets.
built_at_ns.Counter names are written without their
price_level_stream_prefix.What this adds
pamm_quote_stream_v2. Per venue it carries the snapshot Titan priced on, plusthe pair versions of the books that snapshot produced.
venue, the same pair and the same pair version.
PriceLevelStreamState::simulation_overrides()and replay thequote on that state.
spot_price,get_amount_outandget_limitsanswer from theladders alone, and a quote stream frame produces no update of its own.
flowchart LR BK["socket 1: pamm_price_levels_v2<br/>a book: venue + pair<br/>+ calculated_from = pair version"] EN["socket 2: pamm_quote_stream_v2<br/>a venue entry: snapshot<br/>+ pairSequences + built_at_ns"] EN --> BUF["bounded buffer<br/>key: venue + pair<br/>holds a build for one slot"] BK --> J{"same venue,<br/>same pair,<br/>same version?"} BUF --> J J -- yes --> ST["state carries the snapshot"] J -- no --> NS["state serves its ladders,<br/>carries no snapshot"]Decisions a reviewer needs
stamps a batch of pairs with one version, and one captured entry repeats a single version across
eleven pairs. Being in a venue's entry is no match either — the capture holds a Fermi book whose
version that same Fermi entry advertises, but for two other pairs, and that book stays
unstamped. A neighbouring pair's snapshot is never substituted, however close the two look.
accounts, 512 storage words, or 64 KiB of raw slot and word bytes. The buffer holds 8 builds per
venue-and-pair key and 1,024 keys, and reads at most 16 venue entries per frame and 32 pairs per
entry. Past a bound it drops the oldest build of that pair, or the key whose traffic it saw
longest ago. A rotating sweep reaches every key within 32 frames, so a pair Titan stops
streaming does not keep its last build.
versions, no single snapshot explains the served ladders. The state then carries none and counts
as
ambiguous. The ladders are unaffected.narrowing is written on a copy, so the snapshot the buffer still holds for other pairs of the
same entry keeps its own deadline.
without_simulation_overrides()opens no second socket andholds nothing, and the match, buffer and refusal series then do not exist at all. One counter
remains either way —
invalid_provenance_total, because a book's version is read off the pricelevel socket whether or not anything stamps — so its presence is no evidence that stamping is
on.
The replay fence
A build's serving life is fixed when it is first admitted. Titan re-sending an envelope the stream
already holds changes nothing, including the deadline.
That needs bookkeeping when the local clock trails Titan's. A build stamped ahead of the clock
that admits it takes its slot from the admission, not from its stamp. Forgetting such a build and
re-admitting it later would compute a later deadline, handing it a second and longer life. The
stream therefore keeps an empty record of that build — no payload — until one slot after the
build's own stamp. That record serves nothing, answers
expired, and refuses the same payloadagain. Every removal path leaves one behind, and eviction never takes one: under pressure the
arriving build is refused instead.
flowchart TD A["build admitted<br/>serving life fixed here"] --> B{"stamped ahead of<br/>the local clock?"} B -- no --> C["serves its snapshot,<br/>at most one slot"] C --> D["record removed;<br/>may be re-admitted later"] B -- yes --> E["serves its snapshot until<br/>the admission deadline"] E --> F["empty record kept:<br/>serves nothing, answers expired,<br/>refuses the same payload,<br/>cannot be evicted"] F --> G["one slot after the build's<br/>own stamp: removed"]Limits worth knowing
on.
Titan's. Held is not served: it answers
expiredand never matches.as 1 ms is enough to fence every build; one distinct build per second then fills all eight
places of a key, and the newest build is refused under
rejected_key_saturateduntil a placefrees.
snapshot — but recovery by a newer build needs a free place. It is a coverage cost, not a
correctness one. Watch
rejected_key_saturated, and keep the host's clock from drifting behindTitan's.
simulating that leg on mined state can still revert or pay the fallback price.
alone, so a route across several pAMM legs reaches only as far as its weakest leg.
snapshot is the state it priced that version on. Nothing either socket sends lets this code prove
a single simulation produced both. The live check below tests that claim against the chain.
Client-visible change
expires_atis now computed once, from the build's own deadline, and floored once. For somebuilds a client reads a value up to one second later than the previous form reported for the
same build. It is never earlier, and never later than the moment the stream stops serving.
including across a removal and a re-admission.
Operator notes
reconnects_totalandframes_rejected_totalnowcarry a
socketlabel,price_levelsorquote_stream. On deploy each old series goes staleand a labelled one starts at zero — benign for
rateandincrease, and indistinguishable froma restart. Any rule keyed on the exact label set sees both series for the staleness window.
rebuilds with defaults dials
wss://eu.data.titanbuilder.xyz/ws/pamm_quote_stream_v2with nocode change.
without_simulation_overrides()opts out.socket is isolated: it yields no update, removes no component, and its reconnect backoff caps at
32 s. Retries show on
reconnects_total{socket="quote_stream"}and onewarn!per attempt.override_endpoint(url)names the second socket,falling back to
TITAN_PAMM_QUOTE_STREAM_URLand then the EU default. The two sockets of oneregion describe one build round; whether one region's snapshot joins another region's ladders is
unestablished.
override_match_total{outcome}(5 outcomes),override_buffer_total{event}(18 retention events),refused_override_entries_total{reason}(10 producer-side refusals), andinvalid_provenance_total. Read the buffer series as retention, never as coverage. No labelcarries a streamed value.
Dependencies
Two changes in
crates/tycho-simulation/Cargo.toml:serde_jsongains theraw_valuefeature: the lenientcalculated_fromdecode reads aBox<RawValue>, and a dependent resolves features per crate.strumjoinsstrum_macros: the telemetry derives generate impls ofIntoEnumIteratorandEnumCount, whose traits live instrum.The workspace already pins both, so
Cargo.lockgains one edge and--lockedpasses.Coverage, as one capture measured it
In a 75 s capture of both EU sockets, 676 of 1,076 resolved books had a prior exact match (62.8%).
The upper bound is 64.35% if books whose symbols never resolved are credited. For books that did
match, the snapshot arrived a median 495.8 ms, and at most 1.897 s, before its ladders.
Read these as finite-capture observations, not steady-state guarantees. They count raw match
opportunities — before contradiction, before the caps, and before anything admission refuses — so
they are not a predicted attachment rate. An unmatched version means Titan did not publish that
snapshot during this capture, and nothing more. Lower coverage is truthful best effort; it is
never a licence to stamp another pair's snapshot.
Checked against the chain
A snapshot is only worth carrying if it makes the venue price the swap the way the quote said it
would.
crates/tycho-integration-test/tests/pamm_simulation_overrides.rstests exactly that: itcaptures live quotes, waits for the block each quote targets, and simulates the encoded swap at
that block twice — once with the quote's own overrides, once with none.
Two quotes on Ethereum at block 26184402, both USDC -> WETH:
0xb09aaa89…0x666a28140x71e790dd…0x666a28140x666a2814isStaleUpdate(), the venue freshness guard. So the overrides are what makes theswap simulate at all, and the price they produce is the quoted price.
The test is
#[ignore]d, because it dials the live sockets and an RPC. CI runs it only whencrates/tycho-simulation/src/price_level_stream/**or the test itself changes, since each runcosts a live capture and real RPC calls. It skips itself when Titan serves no quote or the RPC
cannot settle the quoted block, so it fails only on a real mismatch. Run it by hand with:
RPC_URL=… cargo test -p tycho-integration-test --test pamm_simulation_overrides \ -- --ignored --nocaptureTwo limits on that evidence. The simulator applies the storage half of a snapshot, so balance and
nonce go unexercised — the freshness guard reads storage. And it simulates at the quoted block once
mined, which is not the parent-anchored call a client makes before the block exists.
Tests
The module suite goes from 151 to 298 tests. Every test runs against local sockets with injected
wall and monotonic clocks; none dials a live endpoint. The capture fixtures are byte-faithful,
because a
serde_json::Valueround trip would narrow every pair version to anf64.Suggested review order
Simulated pre-statesection ofprice_level_stream/mod.rs— the contract this changemakes.
overrides.rs— the buffer and the fence. The retention rules are the part most worthdisagreeing with.
titan.rsandtracker.rs.telemetry.rs, and theSimulation overridessection ofdocs/for-solvers/price-level-stream.md.