fix(storage): keep token amounts exact instead of truncating to uint64 - #2221
Open
AkramBitar wants to merge 2 commits into
Open
fix(storage): keep token amounts exact instead of truncating to uint64#2221AkramBitar wants to merge 2 commits into
AkramBitar wants to merge 2 commits into
Conversation
TokenRecord.Amount was uint64 while the backing column is amount NUMERIC(78, 0), sized for a 256-bit value. AppendToken narrowed the quantity with big.Int.Uint64(), which returns the low 64 bits with no error for anything larger, so for a token whose quantity exceeds 2^64-1 the amount column silently received a wrapped value while the authoritative hex quantity column stayed correct. Widen the field to *big.Int and insert its exact decimal representation, following the pattern already used for movement and transaction records. StoreToken now refuses a nil amount (the column is NOT NULL) and one wider than maxAmountBits, so a value the column cannot hold surfaces as an error instead of a row whose two amount columns disagree. No consumer relied on the uint64 type; the only call sites were the test fixtures updated here. Note that only Postgres can hold such a value: SQLite gives a NUMERIC column NUMERIC affinity and converts an integer literal too large for int64 to REAL. With this change that fails loudly on read rather than corrupting silently. The exact round-trip case is therefore wired into the Postgres suite, while the write-time validation case runs on every backend. Fixes #2021 Signed-off-by: AkramBitar <akram@il.ibm.com>
AkramBitar
force-pushed
the
fix-token-amount-uint64-truncation-2021
branch
from
August 13, 2026 15:00
18556d1 to
b63f3fa
Compare
AkramBitar
requested review from
Effi-S,
SaidAltury-ibm and
adecaro
and removed request for
Effi-S
August 13, 2026 15:18
The amount width check was duplicated across StoreToken, AddTransaction and AddMovement, and the test asserted on a substring of its message. Extract validateAmount and return ErrAmountMissing / ErrAmountOutOfRange from the driver package, following the ErrTokenDoesNotExist convention, so callers and tests can match with errors.Is instead of on message text. Signed-off-by: AkramBitar <akram@il.ibm.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.
Fixes #2021
The problem
Every token has an amount. We store it twice: once as text (the source of truth) and once
as a number, so the database can add amounts up.
The text version can hold very large amounts. The number version was declared as a type
that stops at 20 digits. When a token's amount was bigger than that, the conversion did
not fail or warn — it just kept the leftover part and stored a completely different
number. The two copies then disagreed, and nothing said so.
Nobody notices today because no query uses the number column yet. As soon as one does —
for example to pick which tokens to spend — a huge token could look tiny and the wrong
tokens would be chosen.
The fix
the source of truth exactly.
rejected with a clear error instead of quietly saving something wrong.
one checks the bad cases are refused.
Note for reviewers
No shipped driver can produce such an amount today — fabtoken rejects a precision above 64
bits (
fabtoken/v1/setup/setup.go:256), zkatdlog supports 16/32/64(
zkatdlog/nogh/v1/setup/setup.go:42) — so this is a prerequisite for #2020, not a liveincident.
Also: only Postgres keeps such amounts exactly. SQLite gives a
NUMERICcolumn NUMERICaffinity and converts an integer wider than int64 to REAL, so it cannot store them exactly
whatever the Go type is — after this change that errors on read instead of silently
corrupting. That needs a per-driver column type plus a migration, and it matters for #2020's
index and
ORDER BYonamount; happy to open a separate issue if you'd like it tracked.Checks:
make checksandmake lintclean; storage and token service tests pass,including the PostgreSQL suite.