Merge pull request 'fix(refractor): mask variant hash to 31 bits to fit Postgres INTEGER' (#217) from fix/variant-hash-int32-overflow into main
All checks were successful
Build Docker Image / build (push) Successful in 9m34s
All checks were successful
Build Docker Image / build (push) Successful in 9m34s
This commit is contained in:
commit
f53bc09cbf
@ -322,7 +322,7 @@ def compute_variant_hash(
|
|||||||
identifiers). Order is normalised — callers need not sort.
|
identifiers). Order is normalised — callers need not sort.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A positive integer in the range [1, 2^32 - 1].
|
A positive integer in the range [1, 2^31 - 1].
|
||||||
"""
|
"""
|
||||||
inputs = {
|
inputs = {
|
||||||
"player_id": player_id,
|
"player_id": player_id,
|
||||||
@ -330,7 +330,11 @@ def compute_variant_hash(
|
|||||||
"cosmetics": sorted(cosmetics or []),
|
"cosmetics": sorted(cosmetics or []),
|
||||||
}
|
}
|
||||||
raw = hashlib.sha256(json.dumps(inputs, sort_keys=True).encode()).hexdigest()
|
raw = hashlib.sha256(json.dumps(inputs, sort_keys=True).encode()).hexdigest()
|
||||||
result = int(raw[:8], 16)
|
# Mask to 31 bits so the result fits in Postgres signed INTEGER
|
||||||
|
# (the type used by every variant column). Without this mask, roughly
|
||||||
|
# half of all players would produce hashes above 2^31 - 1 and crash
|
||||||
|
# tier-up writes with peewee.DataError: integer out of range.
|
||||||
|
result = int(raw[:8], 16) & 0x7FFFFFFF
|
||||||
return result if result != 0 else 1 # variant=0 is reserved
|
return result if result != 0 else 1 # variant=0 is reserved
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -878,6 +878,31 @@ class TestVariantHash:
|
|||||||
f"compute_variant_hash({player_id}, {tier}) returned 0"
|
f"compute_variant_hash({player_id}, {tier}) returned 0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_fits_postgres_int32(self):
|
||||||
|
"""Hash always fits in Postgres signed INTEGER range (2^31 - 1).
|
||||||
|
|
||||||
|
What: Generate hashes for player_ids 0-9999 at tiers 0-4 and assert
|
||||||
|
every result is <= 2147483647.
|
||||||
|
|
||||||
|
Why: The variant columns on Card, BattingCard, PitchingCard, and
|
||||||
|
RefractorCardState are Peewee IntegerField → Postgres INTEGER, which
|
||||||
|
is signed 32-bit. Before the 31-bit mask was added, the SHA-256-derived
|
||||||
|
hash could return values up to 2^32 - 1, causing roughly half of all
|
||||||
|
tier-up attempts to crash with peewee.DataError: integer out of range.
|
||||||
|
Discovered via /dev refractor-test on card_id=64460 (Charles Nagy),
|
||||||
|
whose tier-1 hash is 2874960417 — above the int32 ceiling. Masking
|
||||||
|
to 31 bits drops one bit of entropy but keeps ~2.1B distinct values,
|
||||||
|
more than enough for collision safety.
|
||||||
|
"""
|
||||||
|
int32_max = 2147483647
|
||||||
|
for player_id in range(10000):
|
||||||
|
for tier in range(5):
|
||||||
|
result = compute_variant_hash(player_id, tier)
|
||||||
|
assert result <= int32_max, (
|
||||||
|
f"compute_variant_hash({player_id}, {tier}) returned "
|
||||||
|
f"{result}, which exceeds Postgres INTEGER max {int32_max}"
|
||||||
|
)
|
||||||
|
|
||||||
def test_cosmetics_affect_hash(self):
|
def test_cosmetics_affect_hash(self):
|
||||||
"""Adding cosmetics to the same player/tier produces a different hash.
|
"""Adding cosmetics to the same player/tier produces a different hash.
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user