JuliusBrussee/caveman · error

JSON number exponent exceeds supported range

Error message

JSON number exponent exceeds supported range

What it means

The exponent overflow guard in proposalrun's JSON number canonicalizer. maxJSONBNumberExponent is 131072+16383 = 147455, mirroring PostgreSQL's jsonb numeric limits (131072 integer digits, 16383 fraction digits). While accumulating the exponent digits left-to-right, the parser checks value > (max-digit)/10 before each multiply-by-10 step; if the running value would exceed the cap it refuses rather than silently wrapping or letting a hostile exponent drive unbounded arithmetic later.

Source

Thrown at shared/platform/proposalrun/proposalrun.go:289

	if len(raw) == 0 {
		return 0, fmt.Errorf("invalid JSON number exponent")
	}
	negative := false
	if raw[0] == '+' || raw[0] == '-' {
		negative = raw[0] == '-'
		raw = raw[1:]
	}
	if len(raw) == 0 {
		return 0, fmt.Errorf("invalid JSON number exponent")
	}
	value := 0
	for i := 0; i < len(raw); i++ {
		if raw[i] < '0' || raw[i] > '9' {
			return 0, fmt.Errorf("invalid JSON number exponent")
		}
		digit := int(raw[i] - '0')
		if value > (maxJSONBNumberExponent-digit)/10 {
			return 0, fmt.Errorf("JSON number exponent exceeds supported range")
		}
		value = value*10 + digit
	}
	if negative {
		return -value, nil
	}
	return value, nil
}

// formatTime pins the created_at representation: UTC, microsecond resolution
// (Postgres TIMESTAMPTZ stores microseconds, so truncating here makes the value
// hashed at write time equal the value read back at verify time), RFC3339Nano.
func formatTime(t time.Time) string {
	return t.UTC().Truncate(time.Microsecond).Format(time.RFC3339Nano)
}

// RowHash computes the pinned canonical row hash:
//

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Clamp or reject numbers with |exponent| > 147455 before they enter Detail — this ceiling is a hard Postgres jsonb limit, not a tunable.
  2. If huge magnitudes are legitimate, store the value as a string in Detail rather than a JSON number.
  3. Validate with a decimal library (e.g. shopspring/decimal or math/big) and quantize/serialize before marshaling the run row.

Example fix

// before
detail := []byte(`{"prob": 1e-1000000}`)
_, err := proposalrun.RowHash(prev, seq, action, detail, cost, createdAt)

// after
// serialize out-of-range magnitudes as strings
detail := []byte(`{"prob": "1e-1000000"}`)
Defensive patterns

Strategy: validation

Validate before calling

const maxExp = 131072 + 16383 // mirror of maxJSONBNumberExponent
func exponentInRange(f float64) bool {
    if f == 0 { return true }
    _, exp := strconv.FormatFloat(f, 'e', -1, 64) // parse exponent via formatting
    _ = exp
    s := strconv.FormatFloat(f, 'e', -1, 64)
    i := strings.IndexAny(s, "eE")
    n, _ := strconv.Atoi(s[i+1:])
    return n >= -maxExp && n <= maxExp
}

Try / catch

if err := proposalrun.VerifyChain(runs); err != nil {
    if strings.Contains(err.Error(), "exponent exceeds supported range") {
        // re-serialize the offending Detail with out-of-range numbers as strings
    }
}

Prevention

When it happens

Trigger: Any canonicalization of a JSON number whose exponent magnitude exceeds 147455, e.g. 1e999999 or 1e-1000000, while computing RowHash or verifying the chain.

Common situations: Round-tripping values produced by arbitrary-precision libraries (big.Float/Decimal with huge exponents) into a proposal Detail; adversarial input crafted to blow up jsonb storage in Postgres; porting data from systems that allow larger exponents than Postgres jsonb.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/601376bf42dd20af. Report an issue: GitHub.