datahaven/operator/runtime/testnet/src/configs
Ahmad Kaouk 91bab694d8
feat: window-native rewards submission aligned to EigenLayer windows (#471)
## Summary

Replace per-era rewards submission with EigenLayer-aligned window-based
submission. Instead of submitting one rewards message per era, points
are now accumulated into fixed-duration time-aligned windows as blocks
are produced, and inflation is proportionally distributed across windows
at era boundaries. Closed windows are submitted as
`OperatorDirectedRewardsSubmissions` to EigenLayer via Snowbridge.

This ensures rewards submissions match EigenLayer's expected window
boundaries (`GENESIS_REWARDS_TIMESTAMP` + N ×
`CALCULATION_INTERVAL_SECONDS`), eliminating the mismatch between
Substrate era timing and EigenLayer reward windows.

### How rewards windows work

**Points accumulation**: Each time `reward_by_ids()` is called (at
session end), the current timestamp is snapped to the nearest
EigenLayer-aligned window boundary. Points are written to
`WindowOperatorPoints[window_start]`, so all sessions ending within the
same window contribute to the same bucket.

**Inflation allocation**: At era end, the era's scaled inflation is
split across all windows that overlap with the era's time span,
proportionally to the overlap duration:

```
overlap = min(era_end, window_end) - max(era_start, window_start)
portion = scaled_inflation * overlap / era_duration
```

Integer division remainder is assigned to the last overlapping window to
ensure conservation.

**Submission**: `process_closed_windows()` iterates all fully elapsed
windows starting from `NextWindowToSubmit`. For each closed window, if
it has both points and inflation, a cross-chain message is sent via
Snowbridge. Empty or zero-point windows are skipped. The pointer
advances regardless of success or failure.

### Key changes

#### Pallet (`external-validators-rewards`)

- **Window-based point accumulation**: `reward_by_ids()` now writes
operator points into `WindowOperatorPoints[window_start]` in addition to
the existing per-era storage, where the window is determined by snapping
the current timestamp to the nearest EigenLayer-aligned boundary
- **Proportional inflation allocation**: At era end,
`allocate_era_inflation_to_windows()` distributes the era's scaled
inflation across overlapping windows based on time overlap, with
rounding remainder assigned to the last window
- **Closed window submission**: `process_closed_windows()` iterates
fully elapsed windows, combines their points and inflation into a
`RewardsPeriodUtils`, and sends the cross-chain message. A
`NextWindowToSubmit` pointer ensures idempotent progression
- **Removed legacy path**: The per-era `generate_era_rewards_utils` and
direct submission in `on_era_end` are removed. The `rewards_duration()`
method is removed from `RewardsSubmissionConfig` trait — duration now
comes from `RewardsPeriodUtils`
- **Renamed `EraRewardsUtils` to `RewardsPeriodUtils`**: The struct and
its fields are renamed to reflect window/period semantics (`era_index` →
`period_index`, `era_start_timestamp` → `period_start`)
- **New pallet config types**: `RewardsWindowGenesisTimestamp`
(immutable EigenLayer genesis), `RewardsWindowDuration`
(governance-tunable via `RewardsDuration` runtime param), and `UnixTime`
- **Storage version bump**: 0 → 1 for the three new storage items
- **New events**: `RewardsWindowSubmitted`,
`RewardsWindowSubmissionFailed`, `RewardsWindowSkipped` (replaces
`RewardsMessageSent`)

#### Contracts (`DataHavenServiceManager.sol`)

- **Duplicate operator merge**: Replace `_sortOperatorRewards` with
`_sortAndMergeDuplicateOperators` — after solochain-to-ETH address
translation, multiple solochain addresses can map to the same ETH
operator (e.g., operator deregistered and re-registered with a new
solochain address within the same reward window). The new function sorts
then merges consecutive duplicates by summing their amounts, satisfying
EigenLayer's strict ascending uniqueness requirement.

### New storage items

| Storage | Type | Purpose |
|---------|------|---------|
| `WindowOperatorPoints` | `Map<u32, BTreeMap<H160, u32>>` | Per-window
operator reward points |
| `WindowInflationAmount` | `Map<u32, u128>` | Per-window allocated
inflation |
| `NextWindowToSubmit` | `Value<u32>` | Pointer to next window to
process |

### Out of scope (follow-up PRs)

- **Translation robustness**: Retain
`validatorSolochainAddressToEthAddress` on deregistration to prevent
`UnknownSolochainAddress` reverts for delayed window submissions
- **Retry-safe failure handling**: Preserve failed windows for replay
- **Retained window snapshots**: Audit/replay support
- **Benchmarks/weights**: Re-run pallet benchmarks for updated
`on_era_end` weight

## Test plan

- [x] Pallet unit tests pass (`cargo test -p
pallet-external-validators-rewards` — 81 tests)
- [x] `window_mode_attributes_points_to_aligned_window` — points land in
correct aligned window
- [x] `window_mode_submits_closed_windows_and_advances_pointer` — closed
window submission and pointer advancement
- [x] `window_mode_era_inflation_split_across_multiple_windows` —
pro-rata inflation splitting across 4 windows with remainder
conservation
- [x] `window_mode_submits_multiple_closed_windows_in_single_era_end` —
batch submission of multiple closed windows in one `on_era_end`
- [x] `window_mode_delivery_failure_emits_submission_failed_event` —
delivery failure emits `RewardsWindowSubmissionFailed`, clears storage,
advances pointer
- [x] Existing pallet tests updated to use window-based events
(`RewardsWindowSubmitted`, `RewardsWindowSkipped`)
- [x] Contract tests pass (`forge test --match-contract
RewardsSubmitter` — 12 tests)
- [x] `test_submitRewards_mergesDuplicateTranslatedOperators` — two
solochain addresses mapping to same ETH operator are merged into single
entry
- [x] Runtime configs compile with new config types
(mainnet/stagenet/testnet)
- [x] E2E test with local network to verify cross-chain message reaches
EigenLayer contracts

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
2026-04-13 13:12:34 +02:00
..
governance feat: upgrade to polkadot SDK 2503 (#444) 2026-03-26 10:04:57 +01:00
storagehub feat: upgrade to polkadot SDK 2503 (#444) 2026-03-26 10:04:57 +01:00
mod.rs feat: window-native rewards submission aligned to EigenLayer windows (#471) 2026-04-13 13:12:34 +02:00
runtime_params.rs feat: window-native rewards submission aligned to EigenLayer windows (#471) 2026-04-13 13:12:34 +02:00