Repository navigation
perf(signing): encode wide safe integers without BigInt in msgpack - #167
Merged
Merged
Conversation
numberL1 widened every safe integer outside the int32/uint32 forms through BigInt before writing the int64/uint64 form. Every live order ID is wider than 32 bits, so cancel and modify batches allocated one BigInt per entry. Split the double at 2^32 instead and write the two halves with setUint32. The division by a power of two is exact for safe integers, and the unsigned writes wrap negative halves to two's complement, so the bytes are identical to the BigInt path. Adds tests/signing/msgpackWideInt.test.ts, which pins the arm against @std/msgpack and the previous BigInt path across every width boundary and 20000 random wide integers, against hand-derived literals, and through hashes and signatures of representative L1 actions captured before the change. Adds .dev/perf/msgpack_wide_int.ts, a paired micro-benchmark that the fingerprinted suite cannot provide (it has no wide-integer workload). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
MsgpackWriter.numberL1sent every safe integer outside the int32/uint32 forms (>= 2^32or< -2^31) throughBigIntto get the 9-byte int64/uint64 form. Every live order ID is wider than 32 bits, so a cancel or modify batch allocated oneBigIntper entry.The wide arm now splits the double at 2^32 and writes the two halves with
setUint32. The bytes are identical:Math.floor(value / 2 ** 32)gives the exact high word, rounded toward -∞ for negative values.setUint32reduces its argument mod 2^32 (ToUint32). A negative high word such as-1becomes0xffffffff, and the low wordvalue % 2 ** 32(which carries the sign) becomesvalue - floor(value / 2^32) * 2^32. Both halves land in two's complement, and-0becomes0.0xd3for negative values and0xcfotherwise, which matches the oldbigint()path.DataViewdefaults to big-endian, which matchessetBigInt64/setBigUint64and the existinguint64()fast arm.Main's new immutable-action cache (#166) skips re-encoding only for SDK-owned payloads hashed a second time. The first encode of every action still goes through this arm.
Measurements
All numbers are from an Apple M3 Max. Another workload was running on the machine at the same time, so every comparison is paired.
Existing suite (
bun run perf, signing + transaction)I ran 3 paired rounds of base
d3126aeagainst head, alternating order (head/base, base/head, head/base), and compared them with.dev/perf/compare.ts:This is expected. No scenario in
tests/perfhas an integer wider than 32 bits: orders carry none, and the nonce already used theuint64()fast arm. I did not add a scenario there, because editingtests/perfchanges the suite fingerprint and makes the Performance gate fail closed on purpose.Wide-integer micro-benchmark (
.dev/perf/msgpack_wide_int.ts, new)The script builds the pre-change
BigIntwriter from the current_msgpack.tssource in a temp directory. It asserts identical preimage bytes, then times both writers in 15 paired rounds, rotating their order each round. Cancel and modify workloads use realistic order IDs (about 4.1e10). Each cell is the median per-action time from one representative run. The range column covers all repeated runs: 5 on Bun 1.4.0 and 4 on Node 24.10.0.Tests
tests/signing/msgpackWideInt.test.ts(new, 17 tests, about 40k assertions) pins the wide arm three independent ways:@std/msgpackencodingBigInt(value)and against the in-house strictbigintpath the arm replaced. Inputs cover:MAX_SAFE_INTEGERandMIN_SAFE_INTEGERce, 2^32 →cf, −2^31 →d2, −2^31−1 →d3, ±2^53 →cb.d3 ffe0000000000001and −2^31−1 →d3 ffffffff7fffffff.Mutation check: replacing
floorwithtrunc, takingabsof the low half, always taggingcf, or writing little-endian each fails 6–16 of the new tests. A no-op control passes.Test plan
bun run checkbun run test:offline: 2039 pass, 0 failbun run buildbun run perfrounds (signing, transaction), with no regressions.dev/perf/msgpack_wide_int.tsrun 5× on Bun and 4× on Node🤖 Generated with Claude Code