Skip to content

fix: box token gate is the MaxBoxSize parse window, not a count cap#902

Open
mwaddip wants to merge 3 commits into
ergoplatform:developfrom
mwaddip:fix/box-token-window
Open

fix: box token gate is the MaxBoxSize parse window, not a count cap#902
mwaddip wants to merge 3 commits into
ergoplatform:developfrom
mwaddip:fix/box-token-window

Conversation

@mwaddip

@mwaddip mwaddip commented Jun 12, 2026

Copy link
Copy Markdown

The reference impl has no token-count rule at the data layer: nTokens is a bare getUByte() (ErgoBoxCandidate.scala:200); MaxTokens(255) binds only in SDK builders. The real gate is a MaxBoxSize(4096) position-limit window over the candidate span (:191-235), checked before each primitive read (CheckPositionLimit, rule 1014): a final field may overrun the window, a read starting past it throws.

Our 122-token cap diverged both ways: over-rejecting 123-token boxes that fit 4096, and over-accepting reads past the limit.

Commits: (1) position-limit infra mirroring CoreByteReader (one check per primitive; checked get_bytes_into); (2) chunk reads routed through it; (3) the window in the candidate parser + BoxTokens 1..=255 (the box selector keeps its 122 packing policy).

mwaddip and others added 3 commits June 12, 2026 00:57
…ader mirror)

Add a PositionLimit capability to readers and check it once at the START of
each primitive read (get_u8/get_u64/get_bits/new get_bytes_into), failing
strictly on position > limit -- the reference impl's CheckPositionLimit
(validation rule 1014) in CoreByteReader. A primitive that starts within the
limit may run past it unchecked (the JVM's lazy per-read semantics), so the
VLQ continuation loop reads raw bytes.

SigmaByteReader stores the limit (the decorating reader, like
CoreByteReader.positionLimit); bare Cursors are documented no-limit stubs
(the unchecked underlying reader). Behavior-neutral: nothing sets a limit yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Swap raw read_exact for the position-limit-checked get_bytes_into at every
chunk read reachable from consensus parsing (digests/token ids, group
elements, header fields, BigInt/UnsignedBigInt bodies, Coll[Byte] and string
payloads, type-var names, sized-tree bodies) -- the reference impl's getBytes
goes through the checked CoreByteReader at all of these. Behavior-neutral:
no reader sets a limit yet; p2p peer_addr reads stay raw (no window concept
on that path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reference impl has NO token-count rule at the data layer: nTokens is a
bare getUByte() (ErgoBoxCandidate.scala:200) and SigmaConstants.MaxTokens(255)
binds only in SDK builders. The real gate is a MaxBoxSize(4096) position-limit
window over the candidate span (:191-192, restored :235), checked lazily
before each read -- crossing it AT a read is rule 1014, while a final field
may overrun the window.

Our BoundedVec<Token, 1, 122> cap diverged in both directions: a 123-token
box that fits 4096 was over-rejected, and with no window at all a register
read starting past 4096 was over-accepted.

Relax BoxTokens to the u8 wire ceiling (255) and put the window into
parse_box_with_indexed_digests, restored on the success path only like the
JVM (nested candidate parses save/restore the outer window). The box
selector keeps a local fit-derived 122 packing policy so wallet change boxes
stay under the node's size rule; arbitrary candidates are filtered to
window-parseable serializations (anything else cannot round-trip here or on
the JVM, which also retires test_max_tokens in favor of the window pins).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant