fix(hive): accept the ISO form of limit_order_create expiration#375
Merged
Conversation
A live hivehub.dev market order failed with
limit_order_create expiration out of uint32 range: NaN
on the payload
{"orderid":1784583895,"owner":"bithighlander22",
"amount_to_sell":"1.000 HIVE","min_to_receive":"0.048 HBD",
"fill_or_kill":true,"expiration":"2026-08-16T21:44:55"}
Graphene's time_point_sec is a uint32 on the wire, but hived's JSON
representation — and therefore what every Hive library and dApp sends — is a
zone-less ISO 8601 string. The serializer did Number(p.expiration), which is
NaN for that form, so no real dApp could place an order. Every existing test
passed a unix int (1700003600), so the fixtures encoded the wrong assumption
and the whole phase-3 suite went green against a shape nothing sends.
timePointSec() accepts the ISO form, an explicit-offset ISO form, a numeric
string, and a number.
The 'Z' append is the subtle half. Graphene timestamps are always UTC, but JS
parses the zone-less date-time form as LOCAL time (ES2015+), so a naive
Date.parse would have signed an expiration off by the host's UTC offset — six
hours in America/Denver — while showing the user the value they expected. A
wrong-but-plausible expiration is worse than a rejected one. An explicit
offset (…Z, …+02:00) is passed through untouched so it is not double-shifted.
Unparseable input now says what is wanted instead of surfacing NaN, and a
pre-1970 timestamp is rejected by the u32 bound rather than wrapping to a huge
uint32.
Tests: the exact field payload, ISO/unix byte equality, an explicit-offset
case, the numeric-string case, rejection of undefined/null/''/'tomorrow'/{}/NaN,
and a byte-level assertion that a zone-less stamp encodes as UTC. Run green
under TZ=UTC, America/Denver, Asia/Kolkata (half-hour offset) and
Pacific/Auckland.
Differential-verified. Restoring Number() fails 5 of them; dropping only the
'Z' append — the silent-drift mutation — still fails 3, including the
byte-level UTC assertion.
48 pass in hive-ops.test.ts; zero failures across the deterministic suites
(txbuilder, shared, rest-sign-gating).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Date.parse Review finding: Date.parse is permissive in ways that silently CHANGE the value being signed. "2026-02-30T12:00:00" -> rolled forward to 2026-03-02 "August 16, 2026" -> accepted, despite the promised ISO form "08/16/2026" -> accepted, and ambiguous between locales "26-08-16T21:44:55" -> a 2-digit year maps to 1926 via Date.UTC An expiration the user never wrote must be rejected, not normalised — a rolled-forward date is a different order resting on the book for a different window. Now matches an explicit ISO 8601 grammar, then validates the calendar components (month 1-12, day within month with the full Gregorian leap rule, hour/minute/second ranges) before converting. Zone handling is unchanged: zone-less is UTC, an explicit offset is honoured once. Uses setUTCFullYear rather than Date.UTC so years 0-99 are not remapped into the 1900s. Seconds stay optional and fractional seconds are tolerated (hived emits neither, and neither changes the instant). NaN/Infinity keep falling through to the generic "unix seconds or ISO 8601" message rather than the new whole-number one. +7 tests: impossible dates, leap-year boundaries (2028/2000 valid, 2026/2100 not), out-of-range times, six non-ISO formats, 2-digit years, optional/fractional seconds, non-integer numbers. Suite 55/55 under UTC, Denver, Kolkata and Auckland.
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.
The failure
A live
hivehub.dev/market/swaporder:{"orderid":1784583895,"owner":"bithighlander22", "amount_to_sell":"1.000 HIVE","min_to_receive":"0.048 HBD", "fill_or_kill":true,"expiration":"2026-08-16T21:44:55"}Graphene's
time_point_secis a uint32 on the wire, but hived's JSON representation — and therefore what every Hive library and dApp sends — is a zone-less ISO 8601 string.hive-ops.ts:198didNumber(p.expiration), which isNaNfor that form.No real dApp could place an order. Every existing test passed a unix int (
1700003600), so the fixtures encoded the wrong assumption and the entire phase-3 suite went green against a shape nothing actually sends.The subtle half
timePointSec()accepts the ISO form, an explicit-offset ISO form, a numeric string, and a number. The'Z'append is the part worth reviewing carefully:Graphene timestamps are always UTC, but JS parses the zone-less date-time form as local time (ES2015+). A naive
Date.parsewould therefore have signed an expiration off by the host's UTC offset:That fails silently — the order signs, and expires six hours off what the user was shown. A wrong-but-plausible expiration is worse than a rejected one. An explicit offset (
…Z,…+02:00) is passed through untouched so it isn't double-shifted.Unparseable input now names what's wanted instead of surfacing
NaN, and a pre-1970 timestamp is rejected by the existing u32 bound rather than wrapping to a huge uint32.Tests
The exact field payload, ISO/unix byte equality, explicit-offset, numeric-string, rejection of
undefined/null/''/'tomorrow'/{}/NaN, and a byte-level assertion that a zone-less stamp encodes as UTC.Green under four timezones, including a half-hour offset:
Differential-verified. Restoring
Number()fails 5 tests. Dropping only the'Z'append — the silent-drift mutation, the one that still produces a valid signature — still fails 3, including the byte-level UTC assertion.Zero failures across the deterministic suites (
txbuilder,shared,rest-sign-gating).Notes
origin/develop; does not include the unpushed local commits on that branch.operationsthrough verbatim, which is correct: the serializer owns the wire format, so the fix belongs here rather than normalizing in the extension.limit_order_create.expirationis the onlytime_point_secfield in the 14-op clear-sign table; the tx header'sexpirationUnixcomes from Pioneer as an int and is unaffected.🤖 Generated with Claude Code