Skip to content

TIP-833: harden resourceProcessor resource window calculations #833

@halibobo1205

Description

@halibobo1205
tip: 833
title: Harden ResourceProcessor Resource Window Calculations 
author: halibobo1205@gmail.com
status: Draft
type: Standards Track
category: Core
created: 2026-03-05
requires: None

Simple Summary

This proposal hardens all calculation paths in ResourceProcessor that involve chained long-integer multiplication — including increase, increaseV2, unDelegateIncreaseV2, getNewWindowSize, and getUsage — by replacing intermediate arithmetic with BigInteger, upgrading the numeric safety model from "implicitly constrained by runtime context" to "explicitly enforced by the language type."

Motivation

Several methods in ResourceProcessor perform chained long multiplications whose correctness currently relies on implicit range constraints imposed by on-chain parameters. As on-chain bandwidth and energy caps grow, the safety margin provided by those implicit constraints will gradually narrow.

A representative expression from increaseV2() at L134 illustrates the risk:

usage * this.windowSize * WINDOW_SIZE_PRECISION

Key constants and limits:

windowSize            = 86_400_000ms / 3_000ms = 28,800 blocks
WINDOW_SIZE_PRECISION = 1,000
PRECISION             = 1,000,000
Long.MAX_VALUE        = 9,223,372,036,854,775,807

Current safety margin:

Long.MAX_VALUE / windowSize / WINDOW_SIZE_PRECISION ≈ 320,255,973,502 (~320.3 billion)

Note: The current mainnet energy cap is 180 billion (Proposal #97), leaving a safety margin of approximately 1.78×.

The numeric safety hardening follows these principles:

  • Intermediate products are carried in BigInteger, removing all long-width constraints.
  • Before writing back to long, longValueExact() performs an explicit range check; if the result exceeds long range, an ArithmeticException is thrown, the transaction is rejected, and no on-chain state is written.

Specification

All changes described in this section take effect upon activation of a new governance proposal parameter. Until activation, all existing code paths remain completely unchanged.

1. New Helper Method

A private helper method divideCeilExact is added alongside the existing divideCeil, accepting BigInteger arguments and writing back safely via longValueExact():

// ResourceProcessor.java
private static long divideCeilExact(BigInteger numerator, BigInteger denominator) {
    BigInteger[] qr = numerator.divideAndRemainder(denominator);
    BigInteger quotient = qr[1].signum() > 0 ? qr[0].add(BigInteger.ONE) : qr[0];
    return quotient.longValueExact(); // throws ArithmeticException if result exceeds long range ArithmeticException
}

2. Hardened Calculation Paths

increase()

// Before
long averageLastUsage = divideCeil(lastUsage * precision, windowSize);
long averageUsage     = divideCeil(usage * precision, windowSize);

// After
BigInteger biPrecision  = BigInteger.valueOf(precision);
BigInteger biWindowSize = BigInteger.valueOf(windowSize);
long averageLastUsage = divideCeilExact(
    BigInteger.valueOf(lastUsage).multiply(biPrecision), biWindowSize);
long averageUsage = divideCeilExact(
    BigInteger.valueOf(usage).multiply(biPrecision), biWindowSize);

increaseV2()

// Before
long newWindowSize = divideCeil(
    remainUsage * remainWindowSize + usage * this.windowSize * WINDOW_SIZE_PRECISION,
    newUsage);

// After
long newWindowSize = divideCeilExact(
    BigInteger.valueOf(remainUsage)
        .multiply(BigInteger.valueOf(remainWindowSize))
        .add(BigInteger.valueOf(usage)
            .multiply(BigInteger.valueOf(this.windowSize))
            .multiply(BigInteger.valueOf(WINDOW_SIZE_PRECISION))),
    BigInteger.valueOf(newUsage));

unDelegateIncreaseV2()

// Before
long newOwnerWindowSize = divideCeil(
    ownerUsage * remainOwnerWindowSizeV2 + transferUsage * remainReceiverWindowSizeV2,
    newOwnerUsage);

// After
long newOwnerWindowSize = divideCeilExact(
    BigInteger.valueOf(ownerUsage)
        .multiply(BigInteger.valueOf(remainOwnerWindowSizeV2))
        .add(BigInteger.valueOf(transferUsage)
            .multiply(BigInteger.valueOf(remainReceiverWindowSizeV2))),
    BigInteger.valueOf(newOwnerUsage));

getNewWindowSize()

// Before
private long getNewWindowSize(long lastUsage, long lastWindowSize,
    long usage, long windowSize, long newUsage) {
    return (lastUsage * lastWindowSize + usage * windowSize) / newUsage;
}

// After
private long getNewWindowSize(long lastUsage, long lastWindowSize,
    long usage, long windowSize, long newUsage) {
    return BigInteger.valueOf(lastUsage)
        .multiply(BigInteger.valueOf(lastWindowSize))
        .add(BigInteger.valueOf(usage).multiply(BigInteger.valueOf(windowSize)))
        .divide(BigInteger.valueOf(newUsage))
        .longValueExact();
}

getUsage()

// Before
private long getUsage(long usage, long windowSize) {
    return usage * windowSize / precision;
}

private long getUsage(long oldUsage, long oldWindowSize,
    long newUsage, long newWindowSize) {
    return (oldUsage * oldWindowSize + newUsage * newWindowSize) / precision;
}

// After
private long getUsage(long usage, long windowSize) {
    return BigInteger.valueOf(usage)
        .multiply(BigInteger.valueOf(windowSize))
        .divide(BigInteger.valueOf(precision))
        .longValueExact();
}

private long getUsage(long oldUsage, long oldWindowSize,
    long newUsage, long newWindowSize) {
    return BigInteger.valueOf(oldUsage)
        .multiply(BigInteger.valueOf(oldWindowSize))
        .add(BigInteger.valueOf(newUsage).multiply(BigInteger.valueOf(newWindowSize)))
        .divide(BigInteger.valueOf(precision))
        .longValueExact();
}

Rationale

1. BigInteger Is the Least-Invasive Safe Solution for Intermediate Products

StrictMath.multiplyExact() can throw ArithmeticException on a single overflow, but it is unsuitable for the patterns addressed here. In chain expressions such as a * b + c * d or a * b * c, applying multiplyExact to each binary step still requires storing every intermediate result in a long — and that intermediate value may already have overflowed before any check is performed.

BigInteger eliminates intermediate overflow at the root. longValueExact() on the final write-back provides the same ArithmeticException contract as multiplyExact, but fires only when the mathematically correct final result exceeds long range. Compared with replacing numeric types globally, this approach has the smallest possible footprint and a manageable performance impact.

2. Reduced Long-Term Maintenance Risk

The BigInteger path has explicit semantics: intermediate products are unconstrained by long width, the final result is range-checked unconditionally, and the safety guarantee is provided by the language mechanism rather than relying on external parameter constraints. This removes a class of latent risk that grows as on-chain resource limits are raised over time.

Backwards Compatibility

This proposal is gated behind the Proposal mechanism and is fully backwards compatible.

Scenario Behavior
Before activation All existing code paths are completely unchanged
After activation, all intermediate products within long range Results are identical to the old logic
After activation, intermediate product exceeds long range ArithmeticException is thrown; transaction is rejected; no on-chain state is written
Historical block replay Each block follows the branch corresponding to its original on-chain parameters; no impact

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions