Skip to content

Unbounded Meter Data Accumulation Leading to Contract Storage Exhaustion #10

Description

@elizabetheonoja-art

Problem Statement / Feature Objective
The meter aggregation contract appends raw meter readings to a per-device cumulative vector stored in Soroban persistent storage without any pruning or rollup mechanism. Over the lifetime of a device (potentially years), the readings list grows unboundedly, eventually exceeding the contract's storage footprint limit (currently 64KB per contract entry, ~100KB total per Smart Contract according to Stellar protocol limits). An attacker can accelerate this by submitting high-frequency meter readings (every ledger ~5 seconds), causing the storage to fill within days. Once storage is exhausted, all subsequent readings and settlement operations for that device will fail. The objective is to implement a time-windowed rollup mechanism that aggregates raw readings into hourly/daily buckets and prunes raw data older than MAX_RAW_RETENTION_PERIOD.

Technical Invariants & Bounds
Soroban contract storage per-key limit is 64KB; total contract storage is approximately 100KB as of Stellar Protocol 20. Each raw meter reading is a struct of (timestamp: u64, value: i128, source: Address) approximately 56 bytes. At 1 reading every 5 seconds, that is 17,280 readings/day = ~967KB/day — exceeding total storage within 2.5 hours. The rollup bucket must use fixed-point i128 with 7 decimals, summing values as i128 with overflow check via checked_add(). Retention period: MAX_RAW_RETENTION_SECS = 7 * 86400 (7 days). Hourly bucket keys: DataKey::HourlyBucket(device_id, hour_epoch). Daily bucket keys: DataKey::DailyBucket(device_id, day_epoch). The pruning function must delete all DataKey::RawReading(device_id, seq_no) where seq_no corresponds to timestamps older than retention window. Pruning must be done inline during new submissions (amortized O(1) per submission using a watermark cursor stored as DataKey::PruneCursor(device_id)). Each deletion costs ~5,000 instructions; if more than 100 stale entries exist, the prune step must spread across multiple submissions using a batch_prune_count = 10 limit per call.

Codebase Navigation Guide
Meter aggregation logic: contracts/meter-aggregator/src/lib.rs — submit_reading() function at line 89. Reading storage type: contracts/meter-aggregator/src/types.rs — RawReading struct and HourlyBucket struct. Storage key definitions: contracts/meter-aggregator/src/storage.rs. Existing retention constants: likely none or hardcoded in lib.rs. Tests: contracts/meter-aggregator/src/test.rs. The settlement contract reads aggregated data via get_aggregated_volume() at contracts/meter-aggregator/src/lib.rs line 175.

Implementation Blueprint
Step 1: In contracts/meter-aggregator/src/constants.rs, add MAX_RAW_RETENTION_SECS: u64 = 604_800, PRUNE_BATCH_SIZE: u32 = 10, ROLLUP_INTERVAL_HOURS: u64 = 3600. Step 2: Extend DataKey in storage.rs with HourlyBucket(Address, u64), DailyBucket(Address, u64), PruneCursor(Address) variants. Step 3: In submit_reading(), after storing the raw reading, call prune_stale_readings(env, device_id) which reads PruneCursor, iterates up to PRUNE_BATCH_SIZE stale entries, deletes them, updates cursor. Step 4: Add a rollup_raw_to_hourly() helper that atomically reads all raw readings in the current hour window, sums them via checked_add(), writes to HourlyBucket, and clears the raw readings for that hour. Step 5: Update get_aggregated_volume() to preferentially read from DailyBucket if available, falling back to summing hourly buckets, and finally summing raw readings. Step 6: Write tests for: overflow aggregation, pruning boundary (exactly 1 second before retention window), hour-rollup correctness, and storage exhaustion prevention. Step 7: Run cargo test --package meter-aggregator to validate.

Metadata

Metadata

Assignees

Labels

Complexity: HardcoreIssues requiring deep systems-level engineering rigorGrantFox OSSIssue tracked in GrantFox OSSLayer: Core-EngineCore contract engine layerMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignCampaign: Official CampaignType: Core-ArchitectureCore architecture design 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