Skip to content

Volumetric Tariff Tier Transition Race in Multi-Device Meter Aggregation #3

Description

@elizabetheonoja-art

Volumetric Tariff Tier Transition Race in Multi-Device Meter Aggregation

Problem Statement

The contracts/src/tariff/volumetric_tier.rs module applies tiered pricing based on cumulative resource consumption across a billing period. The resolve_tier() function at line 55 reads cumulative_consumption from storage and maps it to a tier range (Tier1: 0-1000, Tier2: 1001-5000, etc.). When two telemetry reports for different devices under the same billing account arrive in the same ledger, both consume() calls read cumulative_consumption = 950, both compute Tier1 rate, both add their amounts (e.g., 75 + 75 = 150), and both write cumulative_consumption = 1100 — losing 50 units of consumption. The account is under-billed because the second device's consumption (75 units in Tier2) is charged at Tier1 rate, and the cumulative total is reduced by 50 units.

State Invariants & Parameters

  • Tier boundaries: T1=[0,1000], T2=(1000,5000], T3=(5000,20000], T4=(20000,∞)
  • MAX_DEVICES_PER_ACCOUNT: 50
  • MAX_TELEMETRY_PER_LEDGER: 500 readings
  • Per-tier rate multipliers: T1=1.0x, T2=1.5x, T3=2.0x, T4=3.0x
  • Invariant: ∀ account: total_billed = Σ(tier_rate_i × consumption_in_tier_i) with no gaps or overlaps

Affected Code Paths

  • contracts/src/tariff/volumetric_tier.rs:50-90 — Read-tier-and-update without atomic lock
  • contracts/src/tariff/aggregator.rs:30-70 — Batch aggregation across devices
  • contracts/src/tariff/tests/volumetric_tier_tests.rs — No concurrent device test

Resolution Blueprint

  1. Implement a per-account consumption accumulator lock using a dedicated storage key CONSUMPTION_LOCK:account_id. Before reading cumulative_consumption, storage_set(&lock_key, &env.current_contract_address()). On conflict (existing lock), defer to next ledger via panic!("retry").
  2. Replace the single cumulative_consumption value with a per-tier consumption vector [t1_used, t2_used, t3_used, t4_used] updated atomically: read all tiers, compute which tier each new consumption falls into, write back. This makes the update idempotent since each tier is independently accumulated.
  3. Add an invariant check in storage: Σ(t_i_used) == cumulative_consumption after every mutation, enforced via debug_assert! in test builds.
  4. Implement a reconciliation function reconcile_account(account_id) in contracts/src/tariff/reconciliation.rs that recalculates total consumption from raw telemetry events if the cumulative value is suspected to be inconsistent.
  5. Add a concurrent device simulation test in contracts/src/tariff/tests/race_volumetric.rs with 10 devices sending 100 readings each under a single account, verifying total_billed == expected_total.

Labels

  • Complexity: Hardcore
  • Layer: Core-Engine
  • Type: Race-Condition

Metadata

Metadata

Assignees

No one assigned

    Labels

    Complexity: HardcoreIssues requiring deep systems-level engineering rigorLayer: Core-EngineCore contract engine layerType: Race-ConditionConcurrency and race condition related issues

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions