Validate child instrument IDs in OrderBookDeltas - #4710
Open
folknor wants to merge 1 commit into
Open
Conversation
`OrderBookDeltas` carries a wrapper `instrument_id` while each child `OrderBookDelta` carries its own, and nothing required the two to agree. `new_checked` validated only that the vector was non-empty, and `apply_deltas` compared the wrapper against the book before delegating to `apply_deltas_unchecked`, which applies every child through `apply_delta_unchecked`, so no child ID was compared to anything. The singular `apply_delta` does check its delta's ID; the batch path had no equivalent. Reject a mismatched batch in `new_checked`. The comparison is pointer-first with a string-value fallback: `InstrumentId` equality is `Ustr` pointer equality, which is the reason `apply_delta_unchecked` exists, so the fallback compares `symbol` and `venue` as strings and accepts identifiers interned in different pools. Ordinary in-process data takes the pointer path. The check is not repeated in `apply_deltas`, where a batch built once may be applied by several consumers. Public fields and the binary decoders still bypass construction; this closes the ordinary route. Coded by an LLM.
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.
OrderBookDeltaspairs a wrapperinstrument_idwith children that each carry their own, and nothing requires the two to agree.Children are never checked against the wrapper
OrderBookDeltas::new_checkedvalidates only that the vector is non-empty, then takesflags,sequence,ts_eventandts_initfrom the last child.OrderBook::apply_deltascompares the wrapper against the book and delegates toapply_deltas_unchecked, which applies each child throughapply_delta_unchecked, so no child's instrument ID is compared to anything. The singularapply_deltadoes check its delta's ID before delegating; the batch path has no equivalent.new_checkednow rejects a batch whose children disagree with the wrapper. The comparison is pointer-first with a string-value fallback, becauseInstrumentIdequality isUstrpointer equality and that is the reasonapply_delta_uncheckedexists at all: the fallback comparessymbolandvenueas strings, so identifiers interned in different pools compare equal when their values match. Ordinary in-process data takes the pointer path and never evaluates the fallback.No in-tree caller is affected, since every built-in assembler resolves one instrument and uses it for the wrapper and each child.
The check is not repeated in
apply_deltas. A batch is constructed once and may be applied by several consumers, and application is the hot path. Construction is also not a complete boundary: the struct's fields are public, the Cap'n Proto and SBE decoders build it literally, and derivedDeserializebypasses it. This closes the ordinary construction route rather than making the type invariant-preserving.Testing
test_order_book_deltas_new_checked_rejects_mismatched_instrumentcovers a mismatched first child and a mismatched later child, so the whole vector is examined rather than its head; a homogeneous multi-child case is the control. Both rejection cases fail against the unfixed constructor.test_apply_deltas_does_not_validate_children_constructed_literallybuilds the struct literally to bypass the constructor and pins what remains:apply_deltasreturnsOkwith the foreign child's level resting in the book, andapply_deltas_uncheckedstill applies one. It records the boundary rather than asserting a rollback that does not occur.Two existing tests met the new contract in opposite ways.
test_order_book_deltas_single_deltapaired aBTCUSD.CRYPTOwrapper with aEURUSD.SIMfixture child while asserting only metadata propagation, and now takes its wrapper from the child. The SBE round-trip fixturetest_order_book_deltas_preserve_delta_instrument_idsis heterogeneous on purpose, since it exists to prove each child's ID is encoded independently, so it constructs its value literally and preserves that coverage; the encoded input is unchanged.