diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 38c231acc..718371936 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -102,8 +102,15 @@ jobs: - name: Generate Matrix id: set-matrix run: | - TESTS=$(cd e2e && go test -list . | grep -v "^ok " | jq -R -s -c 'split("\n")[:-1]') - echo "matrix=${TESTS}" >> $GITHUB_OUTPUT + set -euo pipefail + OUT="$(mktemp)" + ( + cd e2e + # -list also prints package status lines (ok/FAIL). We only want test names. + go test -list '^Test' . | tee "${OUT}" >/dev/null + ) + TESTS="$(grep '^Test' "${OUT}" | jq -R -s -c 'split("\n")[:-1]')" + echo "matrix=${TESTS}" >> "${GITHUB_OUTPUT}" # Test job (depends on prepare, which in turn depends on both build jobs) test: @@ -141,4 +148,4 @@ jobs: run: docker image load -i ${{ env.IBC_TAR_PATH }} - name: Run Tests - run: cd e2e && go test -race -v -timeout 15m -run ^${{ matrix.test }}$ . \ No newline at end of file + run: cd e2e && go test -race -v -timeout 15m -run "^${{ matrix.test }}$" . \ No newline at end of file diff --git a/adr/adr1010 - deferred reporter switch and dispute jail hardening.md b/adr/adr1010 - deferred reporter switch and dispute jail hardening.md new file mode 100644 index 000000000..ffb02ff51 --- /dev/null +++ b/adr/adr1010 - deferred reporter switch and dispute jail hardening.md @@ -0,0 +1,358 @@ +# ADR 1010: Deferred Reporter Switch and Dispute Jail Hardening + +## Authors + +@CJPotter10 + +## Changelog + +- 2026-05-20: initial version +- 2026-05-29: align with implementation (v6.1.6, `dispute_locked_until`, unjail rules); rewrite for broader readability; add FAQ + +--- + +## Summary (plain language) + +Two related changes make reporter switching and dispute penalties fairer and harder to game: + +1. **Deferred reporter switch** — When a selector moves their stake from reporter A to reporter B, the move is **scheduled**, not instant. Their stake stops counting for A right away, but they do not count for B until all reports from the original reporter that include the selector's stake are aggregated. Then the next time the newly selected reporter reports they will formalize the switch and include the selector's stake + +2. **Dispute jail follows the report snapshot** — When a reporter loses a dispute, everyone who contributed stake to **that specific report** is penalized on their own account—not only people still listed under that reporter today. That way, switching to another reporter cannot bring disputed stake back into oracle power before the penalty expires. + +Selectors can still **submit** a switch while dispute-locked; they simply **do not count** toward any reporter’s power until locks expire. + +--- + +## Context + +### Previous behavior (pre-v6.1.6) + +- **`MsgSwitchReporter`** updated which reporter a selector belonged to **immediately**. +- If the outgoing reporter had reporting history, the selector often got a **~21-day wall-clock lock** (`locked_until_time`) so their stake did not count toward **any** reporter for about an unbonding period. + +### Problems that motivated this ADR + +| Problem | Why it mattered | +|--------|------------------| +| Instant switch + long generic lock | Awkward UX; lock was not tied to real oracle commitments | +| Dispute jail only on the reporter row | Selectors could **switch away** and count toward a new reporter while still benefiting from stake that should have been jailed (they were just slashed) | +| One field for everything | Hard to clear dispute penalties without accidentally clearing unrelated legacy locks | + +### Current behavior (v6.1.6+) + +1. **Reporter switch is deferred** — Stake leaves the outgoing reporter immediately; the stored “active reporter” field updates only after an oracle-derived **unlock block height**. New switches do **not** set the old ~21-day `locked_until_time` path. +2. **Dispute jail uses a report snapshot** — Penalties apply to every selector who contributed stake in the disputed report’s **`ReportByBlock`** snapshot (same set used for slashing), via **`dispute_locked_until`** on each selector’s row. +3. **Two separate lock timers on selectors** — See [Selector lock fields](#selector-lock-fields) below. + +--- + +## Selector lock fields + +Each selector has up to three independent mechanisms. Think of them as different “reasons” stake might be excluded: + +| Mechanism | Field / store | What it means | Typical cause | +|-----------|---------------|---------------|---------------| +| **Legacy / non-dispute lock** | `locked_until_time` (wall clock) | Stake excluded everywhere until this time | Pre-upgrade switches; future non-dispute policy | +| **Dispute lock** | `dispute_locked_until` (wall clock) | Stake excluded everywhere until this time | Lost (or pending) dispute on a report they participated in | +| **Switch in progress** | Pending switch rows + `switch_out_locked_until_block` | Stake not on outgoing reporter; not on incoming until finalize | `MsgSwitchReporter` scheduled but not finalized | + +**Reporter row jail** (`OracleReporter.jailed` / `jailed_until`) still applies to addresses that **are** reporters. Selector rows no longer duplicate `jailed` / `jailed_until`; dispute penalties on selectors use **`dispute_locked_until` only**. + +**Stake exclusion rule** — A selector’s bonded stake is skipped when **either** wall-clock lock is active: + +```go +locked_until_time > now OR dispute_locked_until > now +``` + +(Implemented as `SelectorStakeLocked` in `x/reporter/types/selection_lock.go`.) + +**Important separation** + +- Dispute code **never** writes `locked_until_time`. +- Failed disputes and unjail **only** clear `dispute_locked_until` (unless a separate path clears legacy `locked_until_time`). +- If someone has both a legacy lock and a dispute lock, **winning** the dispute removes the dispute part; the legacy lock can remain until it expires. + +--- + +## Overview + +```mermaid +flowchart TB + subgraph oracle [Oracle — each successful report] + B[Record max open query expiration per reporter] + M[(MaxOpenCommitmentByReporter)] + B --> M + end + subgraph switch [Reporter — SwitchReporter message] + G[Read max commitment for outgoing reporter] + P[Schedule pending switch] + G --> P + M -.->|one lookup| G + end + subgraph finalize [Reporter — when stake is recomputed e.g. on SubmitValue] + A[Apply ready pending switches] + F[Finalize: update active reporter] + A --> F + end + P --> finalize +``` + +--- + +## Part 1 — Deferred reporter switch + +### What users experience + +1. Selector sends **`MsgSwitchReporter`** from reporter **A** to **B**. +2. **Immediately:** Their stake **stops counting** toward A. It still **does not** count toward B. +3. **Waiting period:** Chain waits until block height passes an **unlock height** derived from A’s open oracle commitments (see below). Short for simple price feeds; longer when bridge-style queries with far-future expirations were submitted. +4. **Finalization:** The next time stake is recomputed for A or B (typically when either reporter **submits an oracle value**), ready switches are applied. The selector’s active reporter becomes **B**, and stake counts toward B. + +While a **pending row** exists for `(outgoing A, selector)`: + +- Switching to the **same** target again → **no-op** (allowed). +- Switching to a **different** target (e.g. B → C) → **allowed**; replaces the pending row (same `unlock_block` as the original schedule). + +The `switch_out_locked_until_block` gate applies only when there is **no** outgoing pending row but that field is still `>=` current height (should be rare; normally cleared on finalize). In that state, any new switch away from A is rejected until height passes the stored unlock. + +### Unlock timing (intuition) + +| Outgoing reporter’s situation | Unlock height at schedule time | When finalize can run | +|------------------------------|--------------------------------|------------------------| +| No open commitments recorded | `0` | Next stake recompute involving A or B | +| Open work expires at block **H** | **H** (max seen so far) | After chain height **> H** (strictly greater) | +| **H** already in the past | treated as ready | Next stake recompute | + +The oracle keeps a **running maximum** expiration block per reporter (updated on each `MsgSubmitValue`). That value is **fixed** when the switch is scheduled. It only ever increases, so a switch is never shortened by later reports—but an old high expiration can keep a switch pending longer than today’s open queries strictly require. + +**Query types (informal)** + +- **Spot-style** queries → usually short or zero unlock; handoff can complete on the next report. +- **Bridge-style** queries with long expirations → unlock tracks the longest commitment window. + +### Self-reporter demoting to selector + +Extra rules when the selector **is** the reporter row being removed: + +- If other selectors still delegate to them, they must wait **21 days since their last oracle report** before switching away (unchanged). +- They **cannot** demote while they still have **open query commitments** (`max open commitment >= current height`). +- If the reporter row is **jailed**, dispute lock is **copied** to their selector row (`dispute_locked_until`) before the reporter row is removed, so penalties survive demotion. + +### Stake vs identity (phases) + +| Phase | Who the chain says they report with | Counts toward A | Counts toward B | +|-------|-------------------------------------|-----------------|-----------------| +| Right after switch tx | A (unchanged) | No | No | +| After finalize | B | N/A | Yes | + +### Technical state (implementers) + +| Collection | Key | Value | +|------------|-----|--------| +| `OutgoingPendingSwitches` | `(outgoing_reporter, selector)` | `PendingSwitchEntry { to_reporter, unlock_block }` | +| `IncomingPendingSwitchIdx` | `(incoming_reporter, selector)` | outgoing reporter address | +| `ReporterPendingSwitchHeads` | `reporter` | outgoing/incoming counts + min `unlock_block` (fast path before scanning) | + +| `Selection` field | Role | +|-------------------|------| +| `reporter` | Active reporter for indexing; unchanged until finalize | +| `switch_out_locked_until_block` | Copy of scheduled `unlock_block`; blocks a **new** switch only when there is **no** outgoing pending row and this field is `>=` current height | +| `locked_until_time` | Legacy non-dispute exclusion; **not** set on new switches | + +Param **`max_pending_switches_per_reporter`** (default **10**) caps pending rows per outgoing and per incoming reporter. + +**Oracle store:** `MaxOpenCommitmentByReporter` — reporter → max `query.Expiration` block height seen on submit (`bumpMaxOpenCommitmentForReporter` in `submit_value.go`). + +**`MsgSwitchReporter` flow (ordered)** + +1. Validations (min stake, max selectors, reporter exists, not already on target). +2. Idempotent return if pending switch to same target already exists. +3. Self-demote path: 21-day / open-commitment / jail-copy / `Reporters.Remove` as applicable. +4. If **no** outgoing pending row: reject when `switch_out_locked_until_block >= current height`. If a pending row exists, that check is skipped (replace target or no-op instead). +5. `FlagStakeRecalc(outgoing)` → `scheduleReporterSwitch` → `FlagStakeRecalc(incoming)`. +6. On switch, `lazyClearSelectorLocksIfExpired` may clear an **expired** dispute lock and flag recalc. + +**Finalization:** `ReporterStake` calls `applyReadyPendingSwitchesForReporter` first. Condition: `unlock_block < height` (strict). No BeginBlocker dedicated to switches. + +```mermaid +sequenceDiagram + participant S as Selector + participant R as Reporter module + participant O as Oracle + + S->>R: SwitchReporter A → B + R->>O: Max open commitment for A + R->>R: Schedule pending switch + Note over R: Stake off A; reporter field still A + + Note over O: SubmitValue (A or B) + O->>R: ReporterStake + R->>R: Finalize if height > unlock_block + Note over R: reporter = B; stake on B +``` + +--- + +## Part 2 — Dispute jail hardening + +### What users experience + +When a dispute is resolved against a reporter (after fees are paid and slash/jail runs): + +1. **Reporter account** (if it still exists) is marked jailed for the dispute category’s duration. +2. **Every selector in the report snapshot** at the disputed block gets **`dispute_locked_until`** extended to the jail end time (wall clock). This uses the same delegator list as slashing—not “who is indexed under that reporter right now.” +3. While `dispute_locked_until` is in the future, that selector’s stake is **excluded** from **every** reporter’s oracle power and from dispute vote power. +4. **`MsgSwitchReporter` is still allowed**; it does not bypass the lock. + +**Jail durations (examples)** + +| Dispute level | Reporter slash (illustrative) | Jail duration | +|---------------|-------------------------------|---------------| +| Warning | 1% | Effectively immediate unjail eligibility (duration 0) | +| Minor | 5% | **600 seconds** (~10 minutes) | +| Major | 100% | Very long / “until unjail or win” | + +### Unjail and failed disputes + +| Event | Reporter row | Selector `dispute_locked_until` | Selector `locked_until_time` | +|-------|--------------|-----------------------------------|------------------------------| +| **`MsgUnjailReporter`** (self, after sentence) | Cleared if jailed | Cleared if active | **Not** cleared by default | +| **`MsgUnjailReporter`** (third party) | Same | Same | Same | +| Third-party timing | — | Allowed only **7 days after** self-unjail would have been allowed | — | +| **Reporter wins dispute** (`UpdateJailedUntilOnFailedDispute`) | Jail shortened on reporter row | Cleared for snapshot delegators | **Preserved** | + +Self-reporter demotion while jailed: **`copyReporterJailToSelection`** sets `dispute_locked_until` from the reporter’s `JailedUntil` before removing the reporter row. + +### Technical flow (implementers) + +`SlashAndJailReporter` → `EscrowReporterStake` (snapshot) + `JailReporter`: + +1. If reporter row exists: set `Jailed`, `JailedUntil` (max with existing). +2. `jailSelectorsFromReportSnapshot` → `lockSelectorRowDispute` per delegator in `ReportByBlock` at `report.BlockNumber`. + +Failed dispute: `clearSelectorLocksFromReportSnapshot` → `clearDisputeLock` only. + +```mermaid +flowchart LR + D[SlashAndJailReporter] + E[Escrow stake from ReportByBlock snapshot] + J[JailReporter] + S[Jail selectors in same snapshot] + D --> E + D --> J --> S +``` + +**Where stake lock is enforced** + +- `GetReporterStake` / `ReporterStake` +- Dispute voting (`vote.go`) +- Lazy expiry path when reading selectors (`lazyClearSelectorLocksIfExpired`) + +--- + +## Invariants + +1. At most one outgoing pending row per `(outgoing_reporter, selector)`; switching to the same pending target is idempotent. +2. Outgoing reporter stake never includes selectors with an outgoing pending row for that reporter. +3. Incoming reporter stake never includes a selector until finalize sets `Selection.reporter`. +4. Dispute slash, jail, and failed-dispute unlock use the same **`ReportByBlock`** delegator set at `report.BlockNumber`. +5. `unlock_block` on a pending switch is the oracle max-commitment value **at schedule time** (unchanged if the pending row is replaced to a different target—replacement keeps the prior unlock block). +6. Dispute paths never read or write `locked_until_time`; `dispute_locked_until` is never copied into `locked_until_time`. + +--- + +## Upgrade + +**v6.1.6** (`app/upgrades/v6.1.6/`): module migrations; new collections and proto fields default to empty/zero. No custom state walk beyond `RunMigrations`. + +--- + +## FAQ + +### Reporter switching + +**Can I switch reporters while I am dispute-locked?** +Yes. The switch transaction is allowed. Your stake still does **not** count toward **any** reporter (including the new one) until `dispute_locked_until` expires—and until the pending switch finalizes, as usual. + +**When does my stake count toward the new reporter?** +Only after two things: (1) the pending switch **finalizes** (active reporter field updates to the new address), and (2) you are not excluded by `dispute_locked_until` or `locked_until_time`. Finalization usually happens on the next oracle report that triggers stake recompute for the old or new reporter (often the next `SubmitValue`). + +**What triggers finalization of my switch?** +The chain checks pending switches at the start of **reporter stake recompute**—typically when a reporter submits an oracle value. There is no separate “finalize switch” message. If the unlock height is already in the past, the next such event can complete the handoff. + +**Why is my switch still pending?** +The unlock height is based on the **outgoing** reporter’s longest recorded open query expiration at the time you switched. Bridge-style queries with far-future expirations can delay finalization. The stored max only ever increases, so an old high expiration can keep you waiting longer than today’s open queries might suggest. + +**Do new switches still get a ~21-day lock on my stake?** +No. New switches use the **block-height pending handoff**, not a fresh ~21-day `locked_until_time`. You may still have an **old** `locked_until_time` from before the upgrade or from other policy—that is separate. + +**I already submitted a switch to reporter B. Can I submit the same switch again?** +If a pending switch to **B** already exists, sending the same switch again succeeds as a **no-op**. To change target to reporter C, you need a different pending row (subject to caps and lock rules). + +**Can I start another switch while one is in progress?** +**It depends.** If a pending row already exists (A → B in flight): + +- Same target B again → yes, no-op. +- Different target C → yes; pending becomes A → C (original `unlock_block` is kept). + +You cannot have two separate pending handoffs from the same outgoing reporter for the same selector—only one outgoing pending row, which you may **replace**. + +If there is **no** pending row but `switch_out_locked_until_block` is still at or above the current height, a new switch is **rejected** until that height passes (normally this field is cleared when a pending switch finalizes). + +**I am a reporter demoting myself to selector. Anything extra?** +Yes, if others still select you: you must wait **21 days since your last report**. You also cannot demote while you still have **open query commitments**. If your reporter account is jailed, that jail is copied to your selector row before the reporter row is removed. + +### Disputes and locks + +**I switched away before a dispute on my old reporter. Can I still be penalized?** +Yes, if your stake was in the **report snapshot** at the disputed block. Dispute jail uses that snapshot, not “who you report with today.” You get `dispute_locked_until` on your own selector row and your stake is excluded everywhere until it expires. + +**What is the difference between `locked_until_time` and `dispute_locked_until`?** +`locked_until_time` is for **non-dispute** exclusion (e.g. legacy ~21-day locks from older switch behavior). `dispute_locked_until` is **only** for dispute penalties. Dispute code never touches `locked_until_time`. Either one active is enough to exclude your stake. + +**The reporter won the dispute. What happens to me as a selector?** +If you were locked only via **`dispute_locked_until`** from that dispute, that lock is cleared for delegators in the report snapshot. If you also have **`locked_until_time`** (e.g. from an old switch), that stays until its time passes. + +**How long does a minor dispute lock last?** +About **600 seconds** (~10 minutes) of chain time for the jail duration used when setting `dispute_locked_until` (same window as reporter jail for minor disputes). Major disputes use a much longer sentence; warnings are effectively immediate for unjail eligibility. + +**Can I vote in disputes while dispute-locked?** +Your stake is excluded from **reporter oracle power** and from **dispute vote power** while `SelectorStakeLocked` is true (either wall-clock lock field active). + +### Unjail + +**How do I clear a dispute lock on my selector account?** +Wait until `dispute_locked_until` is in the past, then call **`MsgUnjailReporter`** on your own address (if you also have a reporter row, that is cleared too when applicable). The chain may also lazy-clear an expired dispute lock when you interact with reporter messages. + +**Can someone else unjail me?** +A third party can call unjail on your reporter/selector address only after **7 days beyond** when you would have been allowed to unjail yourself. + +**Does unjail remove my old ~21-day `locked_until_time`?** +No. Unjail clears **dispute** lock (`dispute_locked_until`) and reporter-row jail. Legacy `locked_until_time` is unchanged unless a separate path clears it. + +--- + +## Implementation map + +| Area | Path | +|------|------| +| Pending switch | `x/reporter/keeper/pending_switch.go` | +| Switch / create reporter msgs | `x/reporter/keeper/msg_server.go` | +| Stake + finalize entry | `x/reporter/keeper/reporter.go` | +| Jail / unjail | `x/reporter/keeper/jail.go` | +| Stake lock helper | `x/reporter/types/selection_lock.go` | +| Oracle max commitment | `x/oracle/keeper/max_open_commitment.go`, `submit_value.go` | +| Dispute slash/jail | `x/dispute/keeper/dispute.go`, `execute.go` | +| Proto | `proto/layer/reporter/selection.proto`, `params.proto` | +| Chain upgrade | `app/upgrades/v6.1.6/upgrade.go` | + +--- + +## Operational notes + +- **Monotonic max commitment** can defer switches longer than the live set of open queries strictly requires. +- **Self-reporter with other selectors** still requires 21 days since last report before delegating reporting to another reporter. +- **Self-reporter demotion** is blocked while `max open commitment >= current height`. +- Legacy **`locked_until_time`** from pre-upgrade switches continues to exclude stake until it expires; winning a dispute does not remove it. +- **Switch while dispute-locked** is allowed; stake remains excluded until `dispute_locked_until` passes (and any legacy lock). +- Integration coverage: `tests/integration/reporter_switch_test.go`, `e2e/dispute_test.go` (switch + dispute scenarios). diff --git a/api/layer/reporter/params.pulsar.go b/api/layer/reporter/params.pulsar.go index f307a3688..b894742ff 100644 --- a/api/layer/reporter/params.pulsar.go +++ b/api/layer/reporter/params.pulsar.go @@ -17,11 +17,12 @@ import ( ) var ( - md_Params protoreflect.MessageDescriptor - fd_Params_min_commission_rate protoreflect.FieldDescriptor - fd_Params_min_loya protoreflect.FieldDescriptor - fd_Params_max_selectors protoreflect.FieldDescriptor - fd_Params_max_num_of_delegations protoreflect.FieldDescriptor + md_Params protoreflect.MessageDescriptor + fd_Params_min_commission_rate protoreflect.FieldDescriptor + fd_Params_min_loya protoreflect.FieldDescriptor + fd_Params_max_selectors protoreflect.FieldDescriptor + fd_Params_max_num_of_delegations protoreflect.FieldDescriptor + fd_Params_max_pending_switches_per_reporter protoreflect.FieldDescriptor ) func init() { @@ -31,6 +32,7 @@ func init() { fd_Params_min_loya = md_Params.Fields().ByName("min_loya") fd_Params_max_selectors = md_Params.Fields().ByName("max_selectors") fd_Params_max_num_of_delegations = md_Params.Fields().ByName("max_num_of_delegations") + fd_Params_max_pending_switches_per_reporter = md_Params.Fields().ByName("max_pending_switches_per_reporter") } var _ protoreflect.Message = (*fastReflection_Params)(nil) @@ -122,6 +124,12 @@ func (x *fastReflection_Params) Range(f func(protoreflect.FieldDescriptor, proto return } } + if x.MaxPendingSwitchesPerReporter != uint64(0) { + value := protoreflect.ValueOfUint64(x.MaxPendingSwitchesPerReporter) + if !f(fd_Params_max_pending_switches_per_reporter, value) { + return + } + } } // Has reports whether a field is populated. @@ -145,6 +153,8 @@ func (x *fastReflection_Params) Has(fd protoreflect.FieldDescriptor) bool { return x.MaxSelectors != uint64(0) case "layer.reporter.Params.max_num_of_delegations": return x.MaxNumOfDelegations != uint64(0) + case "layer.reporter.Params.max_pending_switches_per_reporter": + return x.MaxPendingSwitchesPerReporter != uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Params")) @@ -169,6 +179,8 @@ func (x *fastReflection_Params) Clear(fd protoreflect.FieldDescriptor) { x.MaxSelectors = uint64(0) case "layer.reporter.Params.max_num_of_delegations": x.MaxNumOfDelegations = uint64(0) + case "layer.reporter.Params.max_pending_switches_per_reporter": + x.MaxPendingSwitchesPerReporter = uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Params")) @@ -197,6 +209,9 @@ func (x *fastReflection_Params) Get(descriptor protoreflect.FieldDescriptor) pro case "layer.reporter.Params.max_num_of_delegations": value := x.MaxNumOfDelegations return protoreflect.ValueOfUint64(value) + case "layer.reporter.Params.max_pending_switches_per_reporter": + value := x.MaxPendingSwitchesPerReporter + return protoreflect.ValueOfUint64(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Params")) @@ -225,6 +240,8 @@ func (x *fastReflection_Params) Set(fd protoreflect.FieldDescriptor, value proto x.MaxSelectors = value.Uint() case "layer.reporter.Params.max_num_of_delegations": x.MaxNumOfDelegations = value.Uint() + case "layer.reporter.Params.max_pending_switches_per_reporter": + x.MaxPendingSwitchesPerReporter = value.Uint() default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Params")) @@ -253,6 +270,8 @@ func (x *fastReflection_Params) Mutable(fd protoreflect.FieldDescriptor) protore panic(fmt.Errorf("field max_selectors of message layer.reporter.Params is not mutable")) case "layer.reporter.Params.max_num_of_delegations": panic(fmt.Errorf("field max_num_of_delegations of message layer.reporter.Params is not mutable")) + case "layer.reporter.Params.max_pending_switches_per_reporter": + panic(fmt.Errorf("field max_pending_switches_per_reporter of message layer.reporter.Params is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Params")) @@ -274,6 +293,8 @@ func (x *fastReflection_Params) NewField(fd protoreflect.FieldDescriptor) protor return protoreflect.ValueOfUint64(uint64(0)) case "layer.reporter.Params.max_num_of_delegations": return protoreflect.ValueOfUint64(uint64(0)) + case "layer.reporter.Params.max_pending_switches_per_reporter": + return protoreflect.ValueOfUint64(uint64(0)) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Params")) @@ -357,6 +378,9 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { if x.MaxNumOfDelegations != 0 { n += 1 + runtime.Sov(uint64(x.MaxNumOfDelegations)) } + if x.MaxPendingSwitchesPerReporter != 0 { + n += 1 + runtime.Sov(uint64(x.MaxPendingSwitchesPerReporter)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -386,6 +410,11 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.MaxPendingSwitchesPerReporter != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.MaxPendingSwitchesPerReporter)) + i-- + dAtA[i] = 0x28 + } if x.MaxNumOfDelegations != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.MaxNumOfDelegations)) i-- @@ -561,6 +590,25 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { break } } + case 5: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field MaxPendingSwitchesPerReporter", wireType) + } + x.MaxPendingSwitchesPerReporter = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.MaxPendingSwitchesPerReporter |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -1122,6 +1170,9 @@ type Params struct { MaxSelectors uint64 `protobuf:"varint,3,opt,name=max_selectors,json=maxSelectors,proto3" json:"max_selectors,omitempty"` // max number of validators a user can delegate too MaxNumOfDelegations uint64 `protobuf:"varint,4,opt,name=max_num_of_delegations,json=maxNumOfDelegations,proto3" json:"max_num_of_delegations,omitempty"` + // max pending reporter switches involving a reporter as outgoing or incoming + // (each side capped separately when scheduling a switch). + MaxPendingSwitchesPerReporter uint64 `protobuf:"varint,5,opt,name=max_pending_switches_per_reporter,json=maxPendingSwitchesPerReporter,proto3" json:"max_pending_switches_per_reporter,omitempty"` } func (x *Params) Reset() { @@ -1172,6 +1223,13 @@ func (x *Params) GetMaxNumOfDelegations() uint64 { return 0 } +func (x *Params) GetMaxPendingSwitchesPerReporter() uint64 { + if x != nil { + return x.MaxPendingSwitchesPerReporter + } + return 0 +} + type StakeTracker struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1227,7 +1285,7 @@ var file_layer_reporter_params_proto_rawDesc = []byte{ 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xe0, 0x02, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x7f, 0x0a, + 0x74, 0x6f, 0x22, 0xaa, 0x03, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x7f, 0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x4f, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x1b, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, @@ -1247,31 +1305,35 @@ var file_layer_reporter_params_proto_rawDesc = []byte{ 0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x66, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x4e, 0x75, 0x6d, 0x4f, 0x66, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x17, 0x6c, 0x61, - 0x79, 0x65, 0x72, 0x2f, 0x78, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2f, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x54, - 0x72, 0x61, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x04, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x0a, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3c, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, - 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, - 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xf2, 0xde, 0x1f, 0x0d, 0x79, 0x61, 0x6d, 0x6c, 0x3a, - 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, - 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0xa9, - 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x72, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x72, 0x42, 0x0b, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x74, 0x65, 0x6c, 0x6c, 0x6f, 0x72, 0x2d, 0x69, 0x6f, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x72, 0xa2, 0x02, 0x03, 0x4c, 0x52, 0x58, 0xaa, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, - 0x72, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0xca, 0x02, 0x0e, 0x4c, 0x61, 0x79, - 0x65, 0x72, 0x5c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0xe2, 0x02, 0x1a, 0x4c, 0x61, - 0x79, 0x65, 0x72, 0x5c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0f, 0x4c, 0x61, 0x79, 0x65, 0x72, - 0x3a, 0x3a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x21, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1d, + 0x6d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, + 0x65, 0x73, 0x50, 0x65, 0x72, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x3a, 0x20, 0xe8, + 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x17, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x78, 0x2f, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2f, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, + 0xa6, 0x01, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x72, + 0x12, 0x40, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x42, 0x04, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x3c, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, + 0x74, 0xf2, 0xde, 0x1f, 0x0d, 0x79, 0x61, 0x6d, 0x6c, 0x3a, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0xa9, 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, + 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x42, + 0x0b, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x65, 0x6c, 0x6c, 0x6f, + 0x72, 0x2d, 0x69, 0x6f, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0xa2, 0x02, 0x03, + 0x4c, 0x52, 0x58, 0xaa, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x65, 0x72, 0xca, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x5c, 0x52, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x72, 0xe2, 0x02, 0x1a, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x5c, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0xea, 0x02, 0x0f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x3a, 0x3a, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/layer/reporter/selection.pulsar.go b/api/layer/reporter/selection.pulsar.go index edb8e5b17..f47095f09 100644 --- a/api/layer/reporter/selection.pulsar.go +++ b/api/layer/reporter/selection.pulsar.go @@ -17,10 +17,12 @@ import ( ) var ( - md_Selection protoreflect.MessageDescriptor - fd_Selection_reporter protoreflect.FieldDescriptor - fd_Selection_locked_until_time protoreflect.FieldDescriptor - fd_Selection_delegations_count protoreflect.FieldDescriptor + md_Selection protoreflect.MessageDescriptor + fd_Selection_reporter protoreflect.FieldDescriptor + fd_Selection_locked_until_time protoreflect.FieldDescriptor + fd_Selection_delegations_count protoreflect.FieldDescriptor + fd_Selection_switch_out_locked_until_block protoreflect.FieldDescriptor + fd_Selection_dispute_locked_until protoreflect.FieldDescriptor ) func init() { @@ -29,6 +31,8 @@ func init() { fd_Selection_reporter = md_Selection.Fields().ByName("reporter") fd_Selection_locked_until_time = md_Selection.Fields().ByName("locked_until_time") fd_Selection_delegations_count = md_Selection.Fields().ByName("delegations_count") + fd_Selection_switch_out_locked_until_block = md_Selection.Fields().ByName("switch_out_locked_until_block") + fd_Selection_dispute_locked_until = md_Selection.Fields().ByName("dispute_locked_until") } var _ protoreflect.Message = (*fastReflection_Selection)(nil) @@ -114,6 +118,18 @@ func (x *fastReflection_Selection) Range(f func(protoreflect.FieldDescriptor, pr return } } + if x.SwitchOutLockedUntilBlock != uint64(0) { + value := protoreflect.ValueOfUint64(x.SwitchOutLockedUntilBlock) + if !f(fd_Selection_switch_out_locked_until_block, value) { + return + } + } + if x.DisputeLockedUntil != nil { + value := protoreflect.ValueOfMessage(x.DisputeLockedUntil.ProtoReflect()) + if !f(fd_Selection_dispute_locked_until, value) { + return + } + } } // Has reports whether a field is populated. @@ -135,6 +151,10 @@ func (x *fastReflection_Selection) Has(fd protoreflect.FieldDescriptor) bool { return x.LockedUntilTime != nil case "layer.reporter.Selection.delegations_count": return x.DelegationsCount != uint64(0) + case "layer.reporter.Selection.switch_out_locked_until_block": + return x.SwitchOutLockedUntilBlock != uint64(0) + case "layer.reporter.Selection.dispute_locked_until": + return x.DisputeLockedUntil != nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Selection")) @@ -157,6 +177,10 @@ func (x *fastReflection_Selection) Clear(fd protoreflect.FieldDescriptor) { x.LockedUntilTime = nil case "layer.reporter.Selection.delegations_count": x.DelegationsCount = uint64(0) + case "layer.reporter.Selection.switch_out_locked_until_block": + x.SwitchOutLockedUntilBlock = uint64(0) + case "layer.reporter.Selection.dispute_locked_until": + x.DisputeLockedUntil = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Selection")) @@ -182,6 +206,12 @@ func (x *fastReflection_Selection) Get(descriptor protoreflect.FieldDescriptor) case "layer.reporter.Selection.delegations_count": value := x.DelegationsCount return protoreflect.ValueOfUint64(value) + case "layer.reporter.Selection.switch_out_locked_until_block": + value := x.SwitchOutLockedUntilBlock + return protoreflect.ValueOfUint64(value) + case "layer.reporter.Selection.dispute_locked_until": + value := x.DisputeLockedUntil + return protoreflect.ValueOfMessage(value.ProtoReflect()) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Selection")) @@ -208,6 +238,10 @@ func (x *fastReflection_Selection) Set(fd protoreflect.FieldDescriptor, value pr x.LockedUntilTime = value.Message().Interface().(*timestamppb.Timestamp) case "layer.reporter.Selection.delegations_count": x.DelegationsCount = value.Uint() + case "layer.reporter.Selection.switch_out_locked_until_block": + x.SwitchOutLockedUntilBlock = value.Uint() + case "layer.reporter.Selection.dispute_locked_until": + x.DisputeLockedUntil = value.Message().Interface().(*timestamppb.Timestamp) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Selection")) @@ -233,10 +267,17 @@ func (x *fastReflection_Selection) Mutable(fd protoreflect.FieldDescriptor) prot x.LockedUntilTime = new(timestamppb.Timestamp) } return protoreflect.ValueOfMessage(x.LockedUntilTime.ProtoReflect()) + case "layer.reporter.Selection.dispute_locked_until": + if x.DisputeLockedUntil == nil { + x.DisputeLockedUntil = new(timestamppb.Timestamp) + } + return protoreflect.ValueOfMessage(x.DisputeLockedUntil.ProtoReflect()) case "layer.reporter.Selection.reporter": panic(fmt.Errorf("field reporter of message layer.reporter.Selection is not mutable")) case "layer.reporter.Selection.delegations_count": panic(fmt.Errorf("field delegations_count of message layer.reporter.Selection is not mutable")) + case "layer.reporter.Selection.switch_out_locked_until_block": + panic(fmt.Errorf("field switch_out_locked_until_block of message layer.reporter.Selection is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Selection")) @@ -257,6 +298,11 @@ func (x *fastReflection_Selection) NewField(fd protoreflect.FieldDescriptor) pro return protoreflect.ValueOfMessage(m.ProtoReflect()) case "layer.reporter.Selection.delegations_count": return protoreflect.ValueOfUint64(uint64(0)) + case "layer.reporter.Selection.switch_out_locked_until_block": + return protoreflect.ValueOfUint64(uint64(0)) + case "layer.reporter.Selection.dispute_locked_until": + m := new(timestamppb.Timestamp) + return protoreflect.ValueOfMessage(m.ProtoReflect()) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.Selection")) @@ -337,6 +383,13 @@ func (x *fastReflection_Selection) ProtoMethods() *protoiface.Methods { if x.DelegationsCount != 0 { n += 1 + runtime.Sov(uint64(x.DelegationsCount)) } + if x.SwitchOutLockedUntilBlock != 0 { + n += 1 + runtime.Sov(uint64(x.SwitchOutLockedUntilBlock)) + } + if x.DisputeLockedUntil != nil { + l = options.Size(x.DisputeLockedUntil) + n += 1 + l + runtime.Sov(uint64(l)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -366,6 +419,25 @@ func (x *fastReflection_Selection) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.DisputeLockedUntil != nil { + encoded, err := options.Marshal(x.DisputeLockedUntil) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x2a + } + if x.SwitchOutLockedUntilBlock != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.SwitchOutLockedUntilBlock)) + i-- + dAtA[i] = 0x20 + } if x.DelegationsCount != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.DelegationsCount)) i-- @@ -445,7 +517,1112 @@ func (x *fastReflection_Selection) ProtoMethods() *protoiface.Methods { if wireType != 2 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Reporter", wireType) } - var byteLen int + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.Reporter = append(x.Reporter[:0], dAtA[iNdEx:postIndex]...) + if x.Reporter == nil { + x.Reporter = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field LockedUntilTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.LockedUntilTime == nil { + x.LockedUntilTime = ×tamppb.Timestamp{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.LockedUntilTime); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field DelegationsCount", wireType) + } + x.DelegationsCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.DelegationsCount |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field SwitchOutLockedUntilBlock", wireType) + } + x.SwitchOutLockedUntilBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.SwitchOutLockedUntilBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field DisputeLockedUntil", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.DisputeLockedUntil == nil { + x.DisputeLockedUntil = ×tamppb.Timestamp{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.DisputeLockedUntil); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + +var ( + md_PendingSwitchEntry protoreflect.MessageDescriptor + fd_PendingSwitchEntry_to_reporter protoreflect.FieldDescriptor + fd_PendingSwitchEntry_unlock_block protoreflect.FieldDescriptor +) + +func init() { + file_layer_reporter_selection_proto_init() + md_PendingSwitchEntry = File_layer_reporter_selection_proto.Messages().ByName("PendingSwitchEntry") + fd_PendingSwitchEntry_to_reporter = md_PendingSwitchEntry.Fields().ByName("to_reporter") + fd_PendingSwitchEntry_unlock_block = md_PendingSwitchEntry.Fields().ByName("unlock_block") +} + +var _ protoreflect.Message = (*fastReflection_PendingSwitchEntry)(nil) + +type fastReflection_PendingSwitchEntry PendingSwitchEntry + +func (x *PendingSwitchEntry) ProtoReflect() protoreflect.Message { + return (*fastReflection_PendingSwitchEntry)(x) +} + +func (x *PendingSwitchEntry) slowProtoReflect() protoreflect.Message { + mi := &file_layer_reporter_selection_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_PendingSwitchEntry_messageType fastReflection_PendingSwitchEntry_messageType +var _ protoreflect.MessageType = fastReflection_PendingSwitchEntry_messageType{} + +type fastReflection_PendingSwitchEntry_messageType struct{} + +func (x fastReflection_PendingSwitchEntry_messageType) Zero() protoreflect.Message { + return (*fastReflection_PendingSwitchEntry)(nil) +} +func (x fastReflection_PendingSwitchEntry_messageType) New() protoreflect.Message { + return new(fastReflection_PendingSwitchEntry) +} +func (x fastReflection_PendingSwitchEntry_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_PendingSwitchEntry +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_PendingSwitchEntry) Descriptor() protoreflect.MessageDescriptor { + return md_PendingSwitchEntry +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_PendingSwitchEntry) Type() protoreflect.MessageType { + return _fastReflection_PendingSwitchEntry_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_PendingSwitchEntry) New() protoreflect.Message { + return new(fastReflection_PendingSwitchEntry) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_PendingSwitchEntry) Interface() protoreflect.ProtoMessage { + return (*PendingSwitchEntry)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_PendingSwitchEntry) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if len(x.ToReporter) != 0 { + value := protoreflect.ValueOfBytes(x.ToReporter) + if !f(fd_PendingSwitchEntry_to_reporter, value) { + return + } + } + if x.UnlockBlock != uint64(0) { + value := protoreflect.ValueOfUint64(x.UnlockBlock) + if !f(fd_PendingSwitchEntry_unlock_block, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_PendingSwitchEntry) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "layer.reporter.PendingSwitchEntry.to_reporter": + return len(x.ToReporter) != 0 + case "layer.reporter.PendingSwitchEntry.unlock_block": + return x.UnlockBlock != uint64(0) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.PendingSwitchEntry")) + } + panic(fmt.Errorf("message layer.reporter.PendingSwitchEntry does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_PendingSwitchEntry) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "layer.reporter.PendingSwitchEntry.to_reporter": + x.ToReporter = nil + case "layer.reporter.PendingSwitchEntry.unlock_block": + x.UnlockBlock = uint64(0) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.PendingSwitchEntry")) + } + panic(fmt.Errorf("message layer.reporter.PendingSwitchEntry does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_PendingSwitchEntry) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "layer.reporter.PendingSwitchEntry.to_reporter": + value := x.ToReporter + return protoreflect.ValueOfBytes(value) + case "layer.reporter.PendingSwitchEntry.unlock_block": + value := x.UnlockBlock + return protoreflect.ValueOfUint64(value) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.PendingSwitchEntry")) + } + panic(fmt.Errorf("message layer.reporter.PendingSwitchEntry does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_PendingSwitchEntry) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "layer.reporter.PendingSwitchEntry.to_reporter": + x.ToReporter = value.Bytes() + case "layer.reporter.PendingSwitchEntry.unlock_block": + x.UnlockBlock = value.Uint() + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.PendingSwitchEntry")) + } + panic(fmt.Errorf("message layer.reporter.PendingSwitchEntry does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_PendingSwitchEntry) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "layer.reporter.PendingSwitchEntry.to_reporter": + panic(fmt.Errorf("field to_reporter of message layer.reporter.PendingSwitchEntry is not mutable")) + case "layer.reporter.PendingSwitchEntry.unlock_block": + panic(fmt.Errorf("field unlock_block of message layer.reporter.PendingSwitchEntry is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.PendingSwitchEntry")) + } + panic(fmt.Errorf("message layer.reporter.PendingSwitchEntry does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_PendingSwitchEntry) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "layer.reporter.PendingSwitchEntry.to_reporter": + return protoreflect.ValueOfBytes(nil) + case "layer.reporter.PendingSwitchEntry.unlock_block": + return protoreflect.ValueOfUint64(uint64(0)) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.PendingSwitchEntry")) + } + panic(fmt.Errorf("message layer.reporter.PendingSwitchEntry does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_PendingSwitchEntry) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in layer.reporter.PendingSwitchEntry", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_PendingSwitchEntry) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_PendingSwitchEntry) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_PendingSwitchEntry) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_PendingSwitchEntry) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*PendingSwitchEntry) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + l = len(x.ToReporter) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.UnlockBlock != 0 { + n += 1 + runtime.Sov(uint64(x.UnlockBlock)) + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*PendingSwitchEntry) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if x.UnlockBlock != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.UnlockBlock)) + i-- + dAtA[i] = 0x10 + } + if len(x.ToReporter) > 0 { + i -= len(x.ToReporter) + copy(dAtA[i:], x.ToReporter) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.ToReporter))) + i-- + dAtA[i] = 0xa + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*PendingSwitchEntry) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: PendingSwitchEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: PendingSwitchEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ToReporter", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.ToReporter = append(x.ToReporter[:0], dAtA[iNdEx:postIndex]...) + if x.ToReporter == nil { + x.ToReporter = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field UnlockBlock", wireType) + } + x.UnlockBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.UnlockBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + +var ( + md_ReporterPendingSwitchHead protoreflect.MessageDescriptor + fd_ReporterPendingSwitchHead_outgoing_count protoreflect.FieldDescriptor + fd_ReporterPendingSwitchHead_outgoing_min_unlock protoreflect.FieldDescriptor + fd_ReporterPendingSwitchHead_incoming_count protoreflect.FieldDescriptor + fd_ReporterPendingSwitchHead_incoming_min_unlock protoreflect.FieldDescriptor +) + +func init() { + file_layer_reporter_selection_proto_init() + md_ReporterPendingSwitchHead = File_layer_reporter_selection_proto.Messages().ByName("ReporterPendingSwitchHead") + fd_ReporterPendingSwitchHead_outgoing_count = md_ReporterPendingSwitchHead.Fields().ByName("outgoing_count") + fd_ReporterPendingSwitchHead_outgoing_min_unlock = md_ReporterPendingSwitchHead.Fields().ByName("outgoing_min_unlock") + fd_ReporterPendingSwitchHead_incoming_count = md_ReporterPendingSwitchHead.Fields().ByName("incoming_count") + fd_ReporterPendingSwitchHead_incoming_min_unlock = md_ReporterPendingSwitchHead.Fields().ByName("incoming_min_unlock") +} + +var _ protoreflect.Message = (*fastReflection_ReporterPendingSwitchHead)(nil) + +type fastReflection_ReporterPendingSwitchHead ReporterPendingSwitchHead + +func (x *ReporterPendingSwitchHead) ProtoReflect() protoreflect.Message { + return (*fastReflection_ReporterPendingSwitchHead)(x) +} + +func (x *ReporterPendingSwitchHead) slowProtoReflect() protoreflect.Message { + mi := &file_layer_reporter_selection_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_ReporterPendingSwitchHead_messageType fastReflection_ReporterPendingSwitchHead_messageType +var _ protoreflect.MessageType = fastReflection_ReporterPendingSwitchHead_messageType{} + +type fastReflection_ReporterPendingSwitchHead_messageType struct{} + +func (x fastReflection_ReporterPendingSwitchHead_messageType) Zero() protoreflect.Message { + return (*fastReflection_ReporterPendingSwitchHead)(nil) +} +func (x fastReflection_ReporterPendingSwitchHead_messageType) New() protoreflect.Message { + return new(fastReflection_ReporterPendingSwitchHead) +} +func (x fastReflection_ReporterPendingSwitchHead_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_ReporterPendingSwitchHead +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_ReporterPendingSwitchHead) Descriptor() protoreflect.MessageDescriptor { + return md_ReporterPendingSwitchHead +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_ReporterPendingSwitchHead) Type() protoreflect.MessageType { + return _fastReflection_ReporterPendingSwitchHead_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_ReporterPendingSwitchHead) New() protoreflect.Message { + return new(fastReflection_ReporterPendingSwitchHead) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_ReporterPendingSwitchHead) Interface() protoreflect.ProtoMessage { + return (*ReporterPendingSwitchHead)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_ReporterPendingSwitchHead) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.OutgoingCount != uint32(0) { + value := protoreflect.ValueOfUint32(x.OutgoingCount) + if !f(fd_ReporterPendingSwitchHead_outgoing_count, value) { + return + } + } + if x.OutgoingMinUnlock != uint64(0) { + value := protoreflect.ValueOfUint64(x.OutgoingMinUnlock) + if !f(fd_ReporterPendingSwitchHead_outgoing_min_unlock, value) { + return + } + } + if x.IncomingCount != uint32(0) { + value := protoreflect.ValueOfUint32(x.IncomingCount) + if !f(fd_ReporterPendingSwitchHead_incoming_count, value) { + return + } + } + if x.IncomingMinUnlock != uint64(0) { + value := protoreflect.ValueOfUint64(x.IncomingMinUnlock) + if !f(fd_ReporterPendingSwitchHead_incoming_min_unlock, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_ReporterPendingSwitchHead) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "layer.reporter.ReporterPendingSwitchHead.outgoing_count": + return x.OutgoingCount != uint32(0) + case "layer.reporter.ReporterPendingSwitchHead.outgoing_min_unlock": + return x.OutgoingMinUnlock != uint64(0) + case "layer.reporter.ReporterPendingSwitchHead.incoming_count": + return x.IncomingCount != uint32(0) + case "layer.reporter.ReporterPendingSwitchHead.incoming_min_unlock": + return x.IncomingMinUnlock != uint64(0) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.ReporterPendingSwitchHead")) + } + panic(fmt.Errorf("message layer.reporter.ReporterPendingSwitchHead does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ReporterPendingSwitchHead) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "layer.reporter.ReporterPendingSwitchHead.outgoing_count": + x.OutgoingCount = uint32(0) + case "layer.reporter.ReporterPendingSwitchHead.outgoing_min_unlock": + x.OutgoingMinUnlock = uint64(0) + case "layer.reporter.ReporterPendingSwitchHead.incoming_count": + x.IncomingCount = uint32(0) + case "layer.reporter.ReporterPendingSwitchHead.incoming_min_unlock": + x.IncomingMinUnlock = uint64(0) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.ReporterPendingSwitchHead")) + } + panic(fmt.Errorf("message layer.reporter.ReporterPendingSwitchHead does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_ReporterPendingSwitchHead) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "layer.reporter.ReporterPendingSwitchHead.outgoing_count": + value := x.OutgoingCount + return protoreflect.ValueOfUint32(value) + case "layer.reporter.ReporterPendingSwitchHead.outgoing_min_unlock": + value := x.OutgoingMinUnlock + return protoreflect.ValueOfUint64(value) + case "layer.reporter.ReporterPendingSwitchHead.incoming_count": + value := x.IncomingCount + return protoreflect.ValueOfUint32(value) + case "layer.reporter.ReporterPendingSwitchHead.incoming_min_unlock": + value := x.IncomingMinUnlock + return protoreflect.ValueOfUint64(value) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.ReporterPendingSwitchHead")) + } + panic(fmt.Errorf("message layer.reporter.ReporterPendingSwitchHead does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ReporterPendingSwitchHead) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "layer.reporter.ReporterPendingSwitchHead.outgoing_count": + x.OutgoingCount = uint32(value.Uint()) + case "layer.reporter.ReporterPendingSwitchHead.outgoing_min_unlock": + x.OutgoingMinUnlock = value.Uint() + case "layer.reporter.ReporterPendingSwitchHead.incoming_count": + x.IncomingCount = uint32(value.Uint()) + case "layer.reporter.ReporterPendingSwitchHead.incoming_min_unlock": + x.IncomingMinUnlock = value.Uint() + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.ReporterPendingSwitchHead")) + } + panic(fmt.Errorf("message layer.reporter.ReporterPendingSwitchHead does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ReporterPendingSwitchHead) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "layer.reporter.ReporterPendingSwitchHead.outgoing_count": + panic(fmt.Errorf("field outgoing_count of message layer.reporter.ReporterPendingSwitchHead is not mutable")) + case "layer.reporter.ReporterPendingSwitchHead.outgoing_min_unlock": + panic(fmt.Errorf("field outgoing_min_unlock of message layer.reporter.ReporterPendingSwitchHead is not mutable")) + case "layer.reporter.ReporterPendingSwitchHead.incoming_count": + panic(fmt.Errorf("field incoming_count of message layer.reporter.ReporterPendingSwitchHead is not mutable")) + case "layer.reporter.ReporterPendingSwitchHead.incoming_min_unlock": + panic(fmt.Errorf("field incoming_min_unlock of message layer.reporter.ReporterPendingSwitchHead is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.ReporterPendingSwitchHead")) + } + panic(fmt.Errorf("message layer.reporter.ReporterPendingSwitchHead does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_ReporterPendingSwitchHead) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "layer.reporter.ReporterPendingSwitchHead.outgoing_count": + return protoreflect.ValueOfUint32(uint32(0)) + case "layer.reporter.ReporterPendingSwitchHead.outgoing_min_unlock": + return protoreflect.ValueOfUint64(uint64(0)) + case "layer.reporter.ReporterPendingSwitchHead.incoming_count": + return protoreflect.ValueOfUint32(uint32(0)) + case "layer.reporter.ReporterPendingSwitchHead.incoming_min_unlock": + return protoreflect.ValueOfUint64(uint64(0)) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.ReporterPendingSwitchHead")) + } + panic(fmt.Errorf("message layer.reporter.ReporterPendingSwitchHead does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_ReporterPendingSwitchHead) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in layer.reporter.ReporterPendingSwitchHead", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_ReporterPendingSwitchHead) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ReporterPendingSwitchHead) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_ReporterPendingSwitchHead) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_ReporterPendingSwitchHead) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*ReporterPendingSwitchHead) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + if x.OutgoingCount != 0 { + n += 1 + runtime.Sov(uint64(x.OutgoingCount)) + } + if x.OutgoingMinUnlock != 0 { + n += 1 + runtime.Sov(uint64(x.OutgoingMinUnlock)) + } + if x.IncomingCount != 0 { + n += 1 + runtime.Sov(uint64(x.IncomingCount)) + } + if x.IncomingMinUnlock != 0 { + n += 1 + runtime.Sov(uint64(x.IncomingMinUnlock)) + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*ReporterPendingSwitchHead) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if x.IncomingMinUnlock != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.IncomingMinUnlock)) + i-- + dAtA[i] = 0x20 + } + if x.IncomingCount != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.IncomingCount)) + i-- + dAtA[i] = 0x18 + } + if x.OutgoingMinUnlock != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.OutgoingMinUnlock)) + i-- + dAtA[i] = 0x10 + } + if x.OutgoingCount != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.OutgoingCount)) + i-- + dAtA[i] = 0x8 + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*ReporterPendingSwitchHead) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: ReporterPendingSwitchHead: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: ReporterPendingSwitchHead: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field OutgoingCount", wireType) + } + x.OutgoingCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.OutgoingCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field OutgoingMinUnlock", wireType) + } + x.OutgoingMinUnlock = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow @@ -455,31 +1632,16 @@ func (x *fastReflection_Selection) ProtoMethods() *protoiface.Methods { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + x.OutgoingMinUnlock |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - x.Reporter = append(x.Reporter[:0], dAtA[iNdEx:postIndex]...) - if x.Reporter == nil { - x.Reporter = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field LockedUntilTime", wireType) + case 3: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field IncomingCount", wireType) } - var msglen int + x.IncomingCount = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow @@ -489,33 +1651,16 @@ func (x *fastReflection_Selection) ProtoMethods() *protoiface.Methods { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + x.IncomingCount |= uint32(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength - } - if postIndex > l { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF - } - if x.LockedUntilTime == nil { - x.LockedUntilTime = ×tamppb.Timestamp{} - } - if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.LockedUntilTime); err != nil { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err - } - iNdEx = postIndex - case 3: + case 4: if wireType != 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field DelegationsCount", wireType) + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field IncomingMinUnlock", wireType) } - x.DelegationsCount = 0 + x.IncomingMinUnlock = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow @@ -525,7 +1670,7 @@ func (x *fastReflection_Selection) ProtoMethods() *protoiface.Methods { } b := dAtA[iNdEx] iNdEx++ - x.DelegationsCount |= uint64(b&0x7F) << shift + x.IncomingMinUnlock |= uint64(b&0x7F) << shift if b < 0x80 { break } @@ -587,7 +1732,7 @@ func (x *IndividualDelegation) ProtoReflect() protoreflect.Message { } func (x *IndividualDelegation) slowProtoReflect() protoreflect.Message { - mi := &file_layer_reporter_selection_proto_msgTypes[1] + mi := &file_layer_reporter_selection_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1107,6 +2252,7 @@ var ( fd_FormattedSelection_delegations_count protoreflect.FieldDescriptor fd_FormattedSelection_delegations_total protoreflect.FieldDescriptor fd_FormattedSelection_individual_delegations protoreflect.FieldDescriptor + fd_FormattedSelection_dispute_locked_until protoreflect.FieldDescriptor ) func init() { @@ -1117,6 +2263,7 @@ func init() { fd_FormattedSelection_delegations_count = md_FormattedSelection.Fields().ByName("delegations_count") fd_FormattedSelection_delegations_total = md_FormattedSelection.Fields().ByName("delegations_total") fd_FormattedSelection_individual_delegations = md_FormattedSelection.Fields().ByName("individual_delegations") + fd_FormattedSelection_dispute_locked_until = md_FormattedSelection.Fields().ByName("dispute_locked_until") } var _ protoreflect.Message = (*fastReflection_FormattedSelection)(nil) @@ -1128,7 +2275,7 @@ func (x *FormattedSelection) ProtoReflect() protoreflect.Message { } func (x *FormattedSelection) slowProtoReflect() protoreflect.Message { - mi := &file_layer_reporter_selection_proto_msgTypes[2] + mi := &file_layer_reporter_selection_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1214,6 +2361,12 @@ func (x *fastReflection_FormattedSelection) Range(f func(protoreflect.FieldDescr return } } + if x.DisputeLockedUntil != nil { + value := protoreflect.ValueOfMessage(x.DisputeLockedUntil.ProtoReflect()) + if !f(fd_FormattedSelection_dispute_locked_until, value) { + return + } + } } // Has reports whether a field is populated. @@ -1239,6 +2392,8 @@ func (x *fastReflection_FormattedSelection) Has(fd protoreflect.FieldDescriptor) return x.DelegationsTotal != "" case "layer.reporter.FormattedSelection.individual_delegations": return len(x.IndividualDelegations) != 0 + case "layer.reporter.FormattedSelection.dispute_locked_until": + return x.DisputeLockedUntil != nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.FormattedSelection")) @@ -1265,6 +2420,8 @@ func (x *fastReflection_FormattedSelection) Clear(fd protoreflect.FieldDescripto x.DelegationsTotal = "" case "layer.reporter.FormattedSelection.individual_delegations": x.IndividualDelegations = nil + case "layer.reporter.FormattedSelection.dispute_locked_until": + x.DisputeLockedUntil = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.FormattedSelection")) @@ -1299,6 +2456,9 @@ func (x *fastReflection_FormattedSelection) Get(descriptor protoreflect.FieldDes } listValue := &_FormattedSelection_5_list{list: &x.IndividualDelegations} return protoreflect.ValueOfList(listValue) + case "layer.reporter.FormattedSelection.dispute_locked_until": + value := x.DisputeLockedUntil + return protoreflect.ValueOfMessage(value.ProtoReflect()) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.FormattedSelection")) @@ -1331,6 +2491,8 @@ func (x *fastReflection_FormattedSelection) Set(fd protoreflect.FieldDescriptor, lv := value.List() clv := lv.(*_FormattedSelection_5_list) x.IndividualDelegations = *clv.list + case "layer.reporter.FormattedSelection.dispute_locked_until": + x.DisputeLockedUntil = value.Message().Interface().(*timestamppb.Timestamp) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.FormattedSelection")) @@ -1362,6 +2524,11 @@ func (x *fastReflection_FormattedSelection) Mutable(fd protoreflect.FieldDescrip } value := &_FormattedSelection_5_list{list: &x.IndividualDelegations} return protoreflect.ValueOfList(value) + case "layer.reporter.FormattedSelection.dispute_locked_until": + if x.DisputeLockedUntil == nil { + x.DisputeLockedUntil = new(timestamppb.Timestamp) + } + return protoreflect.ValueOfMessage(x.DisputeLockedUntil.ProtoReflect()) case "layer.reporter.FormattedSelection.selector": panic(fmt.Errorf("field selector of message layer.reporter.FormattedSelection is not mutable")) case "layer.reporter.FormattedSelection.delegations_count": @@ -1393,6 +2560,9 @@ func (x *fastReflection_FormattedSelection) NewField(fd protoreflect.FieldDescri case "layer.reporter.FormattedSelection.individual_delegations": list := []*IndividualDelegation{} return protoreflect.ValueOfList(&_FormattedSelection_5_list{list: &list}) + case "layer.reporter.FormattedSelection.dispute_locked_until": + m := new(timestamppb.Timestamp) + return protoreflect.ValueOfMessage(m.ProtoReflect()) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: layer.reporter.FormattedSelection")) @@ -1483,6 +2653,10 @@ func (x *fastReflection_FormattedSelection) ProtoMethods() *protoiface.Methods { n += 1 + l + runtime.Sov(uint64(l)) } } + if x.DisputeLockedUntil != nil { + l = options.Size(x.DisputeLockedUntil) + n += 1 + l + runtime.Sov(uint64(l)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -1512,6 +2686,20 @@ func (x *fastReflection_FormattedSelection) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.DisputeLockedUntil != nil { + encoded, err := options.Marshal(x.DisputeLockedUntil) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x32 + } if len(x.IndividualDelegations) > 0 { for iNdEx := len(x.IndividualDelegations) - 1; iNdEx >= 0; iNdEx-- { encoded, err := options.Marshal(x.IndividualDelegations[iNdEx]) @@ -1763,6 +2951,42 @@ func (x *fastReflection_FormattedSelection) ProtoMethods() *protoiface.Methods { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err } iNdEx = postIndex + case 6: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field DisputeLockedUntil", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if x.DisputeLockedUntil == nil { + x.DisputeLockedUntil = ×tamppb.Timestamp{} + } + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.DisputeLockedUntil); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -1820,7 +3044,7 @@ func (x *SelectorShare) ProtoReflect() protoreflect.Message { } func (x *SelectorShare) slowProtoReflect() protoreflect.Message { - mi := &file_layer_reporter_selection_proto_msgTypes[3] + mi := &file_layer_reporter_selection_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2361,7 +3585,7 @@ func (x *PeriodRewardData) ProtoReflect() protoreflect.Message { } func (x *PeriodRewardData) slowProtoReflect() protoreflect.Message { - mi := &file_layer_reporter_selection_proto_msgTypes[4] + mi := &file_layer_reporter_selection_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3049,7 +4273,7 @@ func (x *DistributionQueueItem) ProtoReflect() protoreflect.Message { } func (x *DistributionQueueItem) slowProtoReflect() protoreflect.Message { - mi := &file_layer_reporter_selection_proto_msgTypes[5] + mi := &file_layer_reporter_selection_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3682,7 +4906,7 @@ func (x *DistributionQueueCounter) ProtoReflect() protoreflect.Message { } func (x *DistributionQueueCounter) slowProtoReflect() protoreflect.Message { - mi := &file_layer_reporter_selection_proto_msgTypes[6] + mi := &file_layer_reporter_selection_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4133,11 +5357,17 @@ type Selection struct { // reporter is the address of the reporter being delegated to Reporter []byte `protobuf:"bytes,1,opt,name=reporter,proto3" json:"reporter,omitempty"` - // locked_until_time is the time until which the tokens are locked before they - // can be used for reporting again + // locked_until_time — non-dispute / legacy stake exclusion only (e.g. pre-upgrade switch locks). LockedUntilTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=locked_until_time,json=lockedUntilTime,proto3" json:"locked_until_time,omitempty"` // delegations_count is the number of delegations to the reporter DelegationsCount uint64 `protobuf:"varint,3,opt,name=delegations_count,json=delegationsCount,proto3" json:"delegations_count,omitempty"` + // switch_out_locked_until_block is the block height until which this selector + // cannot initiate another reporter switch while a handoff is incomplete: it + // stores the oracle snapshot (max open query expiration for the outgoing + // reporter at schedule time). Cleared when the pending switch is finalized. + SwitchOutLockedUntilBlock uint64 `protobuf:"varint,4,opt,name=switch_out_locked_until_block,json=switchOutLockedUntilBlock,proto3" json:"switch_out_locked_until_block,omitempty"` + // dispute_locked_until — dispute jail only; max on set; never copied to locked_until_time. + DisputeLockedUntil *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=dispute_locked_until,json=disputeLockedUntil,proto3" json:"dispute_locked_until,omitempty"` } func (x *Selection) Reset() { @@ -4181,6 +5411,125 @@ func (x *Selection) GetDelegationsCount() uint64 { return 0 } +func (x *Selection) GetSwitchOutLockedUntilBlock() uint64 { + if x != nil { + return x.SwitchOutLockedUntilBlock + } + return 0 +} + +func (x *Selection) GetDisputeLockedUntil() *timestamppb.Timestamp { + if x != nil { + return x.DisputeLockedUntil + } + return nil +} + +// Pending switch keyed by (outgoing_reporter, selector) in keeper collections. +type PendingSwitchEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ToReporter []byte `protobuf:"bytes,1,opt,name=to_reporter,json=toReporter,proto3" json:"to_reporter,omitempty"` + UnlockBlock uint64 `protobuf:"varint,2,opt,name=unlock_block,json=unlockBlock,proto3" json:"unlock_block,omitempty"` +} + +func (x *PendingSwitchEntry) Reset() { + *x = PendingSwitchEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_layer_reporter_selection_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PendingSwitchEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PendingSwitchEntry) ProtoMessage() {} + +// Deprecated: Use PendingSwitchEntry.ProtoReflect.Descriptor instead. +func (*PendingSwitchEntry) Descriptor() ([]byte, []int) { + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{1} +} + +func (x *PendingSwitchEntry) GetToReporter() []byte { + if x != nil { + return x.ToReporter + } + return nil +} + +func (x *PendingSwitchEntry) GetUnlockBlock() uint64 { + if x != nil { + return x.UnlockBlock + } + return 0 +} + +// Single-reporter summary for O(1) checks in ReporterStake: one Get per reporter +// to see if any pending switch involving this reporter may be ready at height. +type ReporterPendingSwitchHead struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OutgoingCount uint32 `protobuf:"varint,1,opt,name=outgoing_count,json=outgoingCount,proto3" json:"outgoing_count,omitempty"` + OutgoingMinUnlock uint64 `protobuf:"varint,2,opt,name=outgoing_min_unlock,json=outgoingMinUnlock,proto3" json:"outgoing_min_unlock,omitempty"` + IncomingCount uint32 `protobuf:"varint,3,opt,name=incoming_count,json=incomingCount,proto3" json:"incoming_count,omitempty"` + IncomingMinUnlock uint64 `protobuf:"varint,4,opt,name=incoming_min_unlock,json=incomingMinUnlock,proto3" json:"incoming_min_unlock,omitempty"` +} + +func (x *ReporterPendingSwitchHead) Reset() { + *x = ReporterPendingSwitchHead{} + if protoimpl.UnsafeEnabled { + mi := &file_layer_reporter_selection_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReporterPendingSwitchHead) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReporterPendingSwitchHead) ProtoMessage() {} + +// Deprecated: Use ReporterPendingSwitchHead.ProtoReflect.Descriptor instead. +func (*ReporterPendingSwitchHead) Descriptor() ([]byte, []int) { + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{2} +} + +func (x *ReporterPendingSwitchHead) GetOutgoingCount() uint32 { + if x != nil { + return x.OutgoingCount + } + return 0 +} + +func (x *ReporterPendingSwitchHead) GetOutgoingMinUnlock() uint64 { + if x != nil { + return x.OutgoingMinUnlock + } + return 0 +} + +func (x *ReporterPendingSwitchHead) GetIncomingCount() uint32 { + if x != nil { + return x.IncomingCount + } + return 0 +} + +func (x *ReporterPendingSwitchHead) GetIncomingMinUnlock() uint64 { + if x != nil { + return x.IncomingMinUnlock + } + return 0 +} + // IndividualDelegation represents a single delegation to a validator type IndividualDelegation struct { state protoimpl.MessageState @@ -4196,7 +5545,7 @@ type IndividualDelegation struct { func (x *IndividualDelegation) Reset() { *x = IndividualDelegation{} if protoimpl.UnsafeEnabled { - mi := &file_layer_reporter_selection_proto_msgTypes[1] + mi := &file_layer_reporter_selection_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4210,7 +5559,7 @@ func (*IndividualDelegation) ProtoMessage() {} // Deprecated: Use IndividualDelegation.ProtoReflect.Descriptor instead. func (*IndividualDelegation) Descriptor() ([]byte, []int) { - return file_layer_reporter_selection_proto_rawDescGZIP(), []int{1} + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{3} } func (x *IndividualDelegation) GetValidatorAddress() string { @@ -4244,12 +5593,14 @@ type FormattedSelection struct { DelegationsTotal string `protobuf:"bytes,4,opt,name=delegations_total,json=delegationsTotal,proto3" json:"delegations_total,omitempty"` // individual_delegations contains details of each delegation (only populated when delegations_count > 1) IndividualDelegations []*IndividualDelegation `protobuf:"bytes,5,rep,name=individual_delegations,json=individualDelegations,proto3" json:"individual_delegations,omitempty"` + // dispute_locked_until — dispute jail only (query surface). + DisputeLockedUntil *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=dispute_locked_until,json=disputeLockedUntil,proto3" json:"dispute_locked_until,omitempty"` } func (x *FormattedSelection) Reset() { *x = FormattedSelection{} if protoimpl.UnsafeEnabled { - mi := &file_layer_reporter_selection_proto_msgTypes[2] + mi := &file_layer_reporter_selection_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4263,7 +5614,7 @@ func (*FormattedSelection) ProtoMessage() {} // Deprecated: Use FormattedSelection.ProtoReflect.Descriptor instead. func (*FormattedSelection) Descriptor() ([]byte, []int) { - return file_layer_reporter_selection_proto_rawDescGZIP(), []int{2} + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{4} } func (x *FormattedSelection) GetSelector() string { @@ -4301,6 +5652,13 @@ func (x *FormattedSelection) GetIndividualDelegations() []*IndividualDelegation return nil } +func (x *FormattedSelection) GetDisputeLockedUntil() *timestamppb.Timestamp { + if x != nil { + return x.DisputeLockedUntil + } + return nil +} + type SelectorShare struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4313,7 +5671,7 @@ type SelectorShare struct { func (x *SelectorShare) Reset() { *x = SelectorShare{} if protoimpl.UnsafeEnabled { - mi := &file_layer_reporter_selection_proto_msgTypes[3] + mi := &file_layer_reporter_selection_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4327,7 +5685,7 @@ func (*SelectorShare) ProtoMessage() {} // Deprecated: Use SelectorShare.ProtoReflect.Descriptor instead. func (*SelectorShare) Descriptor() ([]byte, []int) { - return file_layer_reporter_selection_proto_rawDescGZIP(), []int{3} + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{5} } func (x *SelectorShare) GetSelectorAddress() []byte { @@ -4358,7 +5716,7 @@ type PeriodRewardData struct { func (x *PeriodRewardData) Reset() { *x = PeriodRewardData{} if protoimpl.UnsafeEnabled { - mi := &file_layer_reporter_selection_proto_msgTypes[4] + mi := &file_layer_reporter_selection_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4372,7 +5730,7 @@ func (*PeriodRewardData) ProtoMessage() {} // Deprecated: Use PeriodRewardData.ProtoReflect.Descriptor instead. func (*PeriodRewardData) Descriptor() ([]byte, []int) { - return file_layer_reporter_selection_proto_rawDescGZIP(), []int{4} + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{6} } func (x *PeriodRewardData) GetSelectors() []*SelectorShare { @@ -4417,7 +5775,7 @@ type DistributionQueueItem struct { func (x *DistributionQueueItem) Reset() { *x = DistributionQueueItem{} if protoimpl.UnsafeEnabled { - mi := &file_layer_reporter_selection_proto_msgTypes[5] + mi := &file_layer_reporter_selection_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4431,7 +5789,7 @@ func (*DistributionQueueItem) ProtoMessage() {} // Deprecated: Use DistributionQueueItem.ProtoReflect.Descriptor instead. func (*DistributionQueueItem) Descriptor() ([]byte, []int) { - return file_layer_reporter_selection_proto_rawDescGZIP(), []int{5} + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{7} } func (x *DistributionQueueItem) GetReporter() []byte { @@ -4474,7 +5832,7 @@ type DistributionQueueCounter struct { func (x *DistributionQueueCounter) Reset() { *x = DistributionQueueCounter{} if protoimpl.UnsafeEnabled { - mi := &file_layer_reporter_selection_proto_msgTypes[6] + mi := &file_layer_reporter_selection_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4488,7 +5846,7 @@ func (*DistributionQueueCounter) ProtoMessage() {} // Deprecated: Use DistributionQueueCounter.ProtoReflect.Descriptor instead. func (*DistributionQueueCounter) Descriptor() ([]byte, []int) { - return file_layer_reporter_selection_proto_rawDescGZIP(), []int{6} + return file_layer_reporter_selection_proto_rawDescGZIP(), []int{8} } func (x *DistributionQueueCounter) GetHead() uint64 { @@ -4517,7 +5875,7 @@ var file_layer_reporter_selection_proto_rawDesc = []byte{ 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa6, 0x01, 0x0a, 0x09, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc0, 0x02, 0x0a, 0x09, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x50, 0x0a, 0x11, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x5f, @@ -4527,7 +5885,35 @@ var file_layer_reporter_selection_proto_rawDesc = []byte{ 0x52, 0x0f, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x65, - 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa2, + 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, + 0x0a, 0x1d, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x4f, 0x75, 0x74, + 0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x56, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, 0x75, 0x74, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, + 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, + 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x12, 0x64, 0x69, 0x73, 0x70, 0x75, 0x74, 0x65, 0x4c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x22, 0x58, 0x0a, 0x12, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1f, + 0x0a, 0x0b, 0x74, 0x6f, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x74, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, + 0x21, 0x0a, 0x0c, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x22, 0xc9, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x50, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x48, 0x65, 0x61, 0x64, + 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, + 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x4d, 0x69, + 0x6e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6f, 0x6d, + 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0d, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2e, + 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x75, + 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x69, 0x6e, 0x63, + 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x4d, 0x69, 0x6e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0xa2, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x64, 0x69, 0x76, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, @@ -4538,7 +5924,7 @@ var file_layer_reporter_selection_proto_rawDesc = []byte{ 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x22, 0x80, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, + 0x75, 0x6e, 0x74, 0x22, 0xd8, 0x03, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, @@ -4562,64 +5948,69 @@ var file_layer_reporter_selection_proto_rawDesc = []byte{ 0x72, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x49, 0x6e, 0x64, 0x69, 0x76, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x44, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x69, 0x6e, 0x64, 0x69, 0x76, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x44, 0x65, 0x6c, 0x65, 0x67, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x7f, 0x0a, 0x0d, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x43, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x56, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, 0x75, 0x74, + 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x42, 0x08, 0xc8, 0xde, 0x1f, 0x00, 0x90, 0xdf, 0x1f, 0x01, 0x52, 0x12, 0x64, 0x69, 0x73, 0x70, + 0x75, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x22, 0x7f, + 0x0a, 0x0d, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x43, 0x0a, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xc8, 0xde, 0x1f, 0x00, + 0xda, 0xde, 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, + 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, + 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0xfe, 0x01, 0x0a, 0x10, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x12, 0x41, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x2b, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, + 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xd2, + 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x56, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x31, 0xc8, 0xde, 0x1f, + 0x00, 0xda, 0xde, 0x1f, 0x1b, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, + 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x44, 0x65, 0x63, + 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x52, 0x0c, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x22, 0x8b, 0x02, 0x0a, 0x15, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x61, 0x79, 0x65, + 0x72, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xfe, 0x01, 0x0a, 0x10, 0x50, 0x65, 0x72, 0x69, - 0x6f, 0x64, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x09, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, - 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x09, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, - 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, - 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, - 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x56, 0x0a, 0x0d, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x31, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x1b, 0x63, 0x6f, 0x73, - 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x4c, - 0x65, 0x67, 0x61, 0x63, 0x79, 0x44, 0x65, 0x63, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, - 0x6f, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x8b, 0x02, 0x0a, 0x15, 0x44, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x49, 0x74, - 0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x3b, - 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x72, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, - 0x52, 0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2b, 0xc8, 0xde, 0x1f, 0x00, - 0xda, 0xde, 0x1f, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, - 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, - 0x6d, 0x6f, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x56, - 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x31, 0xc8, 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x1b, 0x63, - 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, - 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x44, 0x65, 0x63, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, - 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65, 0x63, 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x42, 0x0a, 0x18, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x42, 0xac, 0x01, 0x0a, 0x12, 0x63, - 0x6f, 0x6d, 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x72, 0x42, 0x0e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x74, 0x65, 0x6c, 0x6c, 0x6f, 0x72, 0x2d, 0x69, 0x6f, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x72, 0xa2, 0x02, 0x03, 0x4c, 0x52, 0x58, 0xaa, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0xca, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, - 0x72, 0x5c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0xe2, 0x02, 0x1a, 0x4c, 0x61, 0x79, - 0x65, 0x72, 0x5c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x3a, - 0x3a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x56, 0x0a, 0x0d, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x31, 0xc8, + 0xde, 0x1f, 0x00, 0xda, 0xde, 0x1f, 0x1b, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, + 0x2e, 0x69, 0x6f, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x44, + 0x65, 0x63, 0xd2, 0xb4, 0x2d, 0x0a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x44, 0x65, 0x63, + 0x52, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x42, + 0x0a, 0x18, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, + 0x65, 0x75, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, + 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x61, + 0x69, 0x6c, 0x42, 0xac, 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x61, 0x79, 0x65, 0x72, + 0x2e, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x42, 0x0e, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x65, 0x6c, 0x6c, 0x6f, 0x72, 0x2d, 0x69, + 0x6f, 0x2f, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6c, 0x61, 0x79, 0x65, + 0x72, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0xa2, 0x02, 0x03, 0x4c, 0x52, 0x58, + 0xaa, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, + 0x72, 0xca, 0x02, 0x0e, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x5c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x72, 0xe2, 0x02, 0x1a, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x5c, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x72, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x0f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x3a, 0x3a, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, + 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -4634,28 +6025,32 @@ func file_layer_reporter_selection_proto_rawDescGZIP() []byte { return file_layer_reporter_selection_proto_rawDescData } -var file_layer_reporter_selection_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_layer_reporter_selection_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_layer_reporter_selection_proto_goTypes = []interface{}{ - (*Selection)(nil), // 0: layer.reporter.Selection - (*IndividualDelegation)(nil), // 1: layer.reporter.IndividualDelegation - (*FormattedSelection)(nil), // 2: layer.reporter.FormattedSelection - (*SelectorShare)(nil), // 3: layer.reporter.SelectorShare - (*PeriodRewardData)(nil), // 4: layer.reporter.PeriodRewardData - (*DistributionQueueItem)(nil), // 5: layer.reporter.DistributionQueueItem - (*DistributionQueueCounter)(nil), // 6: layer.reporter.DistributionQueueCounter - (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp + (*Selection)(nil), // 0: layer.reporter.Selection + (*PendingSwitchEntry)(nil), // 1: layer.reporter.PendingSwitchEntry + (*ReporterPendingSwitchHead)(nil), // 2: layer.reporter.ReporterPendingSwitchHead + (*IndividualDelegation)(nil), // 3: layer.reporter.IndividualDelegation + (*FormattedSelection)(nil), // 4: layer.reporter.FormattedSelection + (*SelectorShare)(nil), // 5: layer.reporter.SelectorShare + (*PeriodRewardData)(nil), // 6: layer.reporter.PeriodRewardData + (*DistributionQueueItem)(nil), // 7: layer.reporter.DistributionQueueItem + (*DistributionQueueCounter)(nil), // 8: layer.reporter.DistributionQueueCounter + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp } var file_layer_reporter_selection_proto_depIdxs = []int32{ - 7, // 0: layer.reporter.Selection.locked_until_time:type_name -> google.protobuf.Timestamp - 7, // 1: layer.reporter.FormattedSelection.locked_until_time:type_name -> google.protobuf.Timestamp - 1, // 2: layer.reporter.FormattedSelection.individual_delegations:type_name -> layer.reporter.IndividualDelegation - 3, // 3: layer.reporter.PeriodRewardData.selectors:type_name -> layer.reporter.SelectorShare - 3, // 4: layer.reporter.DistributionQueueItem.selectors:type_name -> layer.reporter.SelectorShare - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 9, // 0: layer.reporter.Selection.locked_until_time:type_name -> google.protobuf.Timestamp + 9, // 1: layer.reporter.Selection.dispute_locked_until:type_name -> google.protobuf.Timestamp + 9, // 2: layer.reporter.FormattedSelection.locked_until_time:type_name -> google.protobuf.Timestamp + 3, // 3: layer.reporter.FormattedSelection.individual_delegations:type_name -> layer.reporter.IndividualDelegation + 9, // 4: layer.reporter.FormattedSelection.dispute_locked_until:type_name -> google.protobuf.Timestamp + 5, // 5: layer.reporter.PeriodRewardData.selectors:type_name -> layer.reporter.SelectorShare + 5, // 6: layer.reporter.DistributionQueueItem.selectors:type_name -> layer.reporter.SelectorShare + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_layer_reporter_selection_proto_init() } @@ -4677,7 +6072,7 @@ func file_layer_reporter_selection_proto_init() { } } file_layer_reporter_selection_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IndividualDelegation); i { + switch v := v.(*PendingSwitchEntry); i { case 0: return &v.state case 1: @@ -4689,7 +6084,7 @@ func file_layer_reporter_selection_proto_init() { } } file_layer_reporter_selection_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FormattedSelection); i { + switch v := v.(*ReporterPendingSwitchHead); i { case 0: return &v.state case 1: @@ -4701,7 +6096,7 @@ func file_layer_reporter_selection_proto_init() { } } file_layer_reporter_selection_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SelectorShare); i { + switch v := v.(*IndividualDelegation); i { case 0: return &v.state case 1: @@ -4713,7 +6108,7 @@ func file_layer_reporter_selection_proto_init() { } } file_layer_reporter_selection_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeriodRewardData); i { + switch v := v.(*FormattedSelection); i { case 0: return &v.state case 1: @@ -4725,7 +6120,7 @@ func file_layer_reporter_selection_proto_init() { } } file_layer_reporter_selection_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DistributionQueueItem); i { + switch v := v.(*SelectorShare); i { case 0: return &v.state case 1: @@ -4737,6 +6132,30 @@ func file_layer_reporter_selection_proto_init() { } } file_layer_reporter_selection_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeriodRewardData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_layer_reporter_selection_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DistributionQueueItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_layer_reporter_selection_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DistributionQueueCounter); i { case 0: return &v.state @@ -4755,7 +6174,7 @@ func file_layer_reporter_selection_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_layer_reporter_selection_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/api/layer/reporter/tx.pulsar.go b/api/layer/reporter/tx.pulsar.go index 959144e0e..441f752bf 100644 --- a/api/layer/reporter/tx.pulsar.go +++ b/api/layer/reporter/tx.pulsar.go @@ -4363,12 +4363,14 @@ func (x *fastReflection_MsgRemoveSelectorResponse) ProtoMethods() *protoiface.Me var ( md_MsgUnjailReporter protoreflect.MessageDescriptor + fd_MsgUnjailReporter_signer_address protoreflect.FieldDescriptor fd_MsgUnjailReporter_reporter_address protoreflect.FieldDescriptor ) func init() { file_layer_reporter_tx_proto_init() md_MsgUnjailReporter = File_layer_reporter_tx_proto.Messages().ByName("MsgUnjailReporter") + fd_MsgUnjailReporter_signer_address = md_MsgUnjailReporter.Fields().ByName("signer_address") fd_MsgUnjailReporter_reporter_address = md_MsgUnjailReporter.Fields().ByName("reporter_address") } @@ -4437,6 +4439,12 @@ func (x *fastReflection_MsgUnjailReporter) Interface() protoreflect.ProtoMessage // While iterating, mutating operations may only be performed // on the current field descriptor. func (x *fastReflection_MsgUnjailReporter) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.SignerAddress != "" { + value := protoreflect.ValueOfString(x.SignerAddress) + if !f(fd_MsgUnjailReporter_signer_address, value) { + return + } + } if x.ReporterAddress != "" { value := protoreflect.ValueOfString(x.ReporterAddress) if !f(fd_MsgUnjailReporter_reporter_address, value) { @@ -4458,6 +4466,8 @@ func (x *fastReflection_MsgUnjailReporter) Range(f func(protoreflect.FieldDescri // a repeated field is populated if it is non-empty. func (x *fastReflection_MsgUnjailReporter) Has(fd protoreflect.FieldDescriptor) bool { switch fd.FullName() { + case "layer.reporter.MsgUnjailReporter.signer_address": + return x.SignerAddress != "" case "layer.reporter.MsgUnjailReporter.reporter_address": return x.ReporterAddress != "" default: @@ -4476,6 +4486,8 @@ func (x *fastReflection_MsgUnjailReporter) Has(fd protoreflect.FieldDescriptor) // Clear is a mutating operation and unsafe for concurrent use. func (x *fastReflection_MsgUnjailReporter) Clear(fd protoreflect.FieldDescriptor) { switch fd.FullName() { + case "layer.reporter.MsgUnjailReporter.signer_address": + x.SignerAddress = "" case "layer.reporter.MsgUnjailReporter.reporter_address": x.ReporterAddress = "" default: @@ -4494,6 +4506,9 @@ func (x *fastReflection_MsgUnjailReporter) Clear(fd protoreflect.FieldDescriptor // of the value; to obtain a mutable reference, use Mutable. func (x *fastReflection_MsgUnjailReporter) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { switch descriptor.FullName() { + case "layer.reporter.MsgUnjailReporter.signer_address": + value := x.SignerAddress + return protoreflect.ValueOfString(value) case "layer.reporter.MsgUnjailReporter.reporter_address": value := x.ReporterAddress return protoreflect.ValueOfString(value) @@ -4517,6 +4532,8 @@ func (x *fastReflection_MsgUnjailReporter) Get(descriptor protoreflect.FieldDesc // Set is a mutating operation and unsafe for concurrent use. func (x *fastReflection_MsgUnjailReporter) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { switch fd.FullName() { + case "layer.reporter.MsgUnjailReporter.signer_address": + x.SignerAddress = value.Interface().(string) case "layer.reporter.MsgUnjailReporter.reporter_address": x.ReporterAddress = value.Interface().(string) default: @@ -4539,6 +4556,8 @@ func (x *fastReflection_MsgUnjailReporter) Set(fd protoreflect.FieldDescriptor, // Mutable is a mutating operation and unsafe for concurrent use. func (x *fastReflection_MsgUnjailReporter) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { switch fd.FullName() { + case "layer.reporter.MsgUnjailReporter.signer_address": + panic(fmt.Errorf("field signer_address of message layer.reporter.MsgUnjailReporter is not mutable")) case "layer.reporter.MsgUnjailReporter.reporter_address": panic(fmt.Errorf("field reporter_address of message layer.reporter.MsgUnjailReporter is not mutable")) default: @@ -4554,6 +4573,8 @@ func (x *fastReflection_MsgUnjailReporter) Mutable(fd protoreflect.FieldDescript // For lists, maps, and messages, this returns a new, empty, mutable value. func (x *fastReflection_MsgUnjailReporter) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { switch fd.FullName() { + case "layer.reporter.MsgUnjailReporter.signer_address": + return protoreflect.ValueOfString("") case "layer.reporter.MsgUnjailReporter.reporter_address": return protoreflect.ValueOfString("") default: @@ -4625,6 +4646,10 @@ func (x *fastReflection_MsgUnjailReporter) ProtoMethods() *protoiface.Methods { var n int var l int _ = l + l = len(x.SignerAddress) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } l = len(x.ReporterAddress) if l > 0 { n += 1 + l + runtime.Sov(uint64(l)) @@ -4663,6 +4688,13 @@ func (x *fastReflection_MsgUnjailReporter) ProtoMethods() *protoiface.Methods { copy(dAtA[i:], x.ReporterAddress) i = runtime.EncodeVarint(dAtA, i, uint64(len(x.ReporterAddress))) i-- + dAtA[i] = 0x12 + } + if len(x.SignerAddress) > 0 { + i -= len(x.SignerAddress) + copy(dAtA[i:], x.SignerAddress) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.SignerAddress))) + i-- dAtA[i] = 0xa } if input.Buf != nil { @@ -4715,6 +4747,38 @@ func (x *fastReflection_MsgUnjailReporter) ProtoMethods() *protoiface.Methods { } switch fieldNum { case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field SignerAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.SignerAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ReporterAddress", wireType) } @@ -7347,7 +7411,10 @@ type MsgUnjailReporter struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ReporterAddress string `protobuf:"bytes,1,opt,name=reporter_address,json=reporterAddress,proto3" json:"reporter_address,omitempty"` + // signer_address is the transaction signer (self-unjail when equal to reporter_address). + SignerAddress string `protobuf:"bytes,1,opt,name=signer_address,json=signerAddress,proto3" json:"signer_address,omitempty"` + // reporter_address is the jailed reporter or selector to unjail. + ReporterAddress string `protobuf:"bytes,2,opt,name=reporter_address,json=reporterAddress,proto3" json:"reporter_address,omitempty"` } func (x *MsgUnjailReporter) Reset() { @@ -7370,6 +7437,13 @@ func (*MsgUnjailReporter) Descriptor() ([]byte, []int) { return file_layer_reporter_tx_proto_rawDescGZIP(), []int{10} } +func (x *MsgUnjailReporter) GetSignerAddress() string { + if x != nil { + return x.SignerAddress + } + return "" +} + func (x *MsgUnjailReporter) GetReporterAddress() string { if x != nil { return x.ReporterAddress @@ -7658,14 +7732,18 @@ var file_layer_reporter_tx_proto_rawDesc = []byte{ 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x10, 0x82, 0xe7, 0xb0, 0x2a, 0x0b, 0x61, 0x6e, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x4d, 0x73, 0x67, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x77, 0x0a, 0x11, 0x4d, 0x73, 0x67, 0x55, 0x6e, - 0x6a, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x10, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, - 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x3a, 0x1d, 0x88, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x00, 0x82, 0xe7, 0xb0, 0x2a, 0x10, - 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb6, 0x01, 0x0a, 0x11, 0x4d, 0x73, 0x67, 0x55, + 0x6e, 0x6a, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x3f, 0x0a, + 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, + 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, + 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x43, + 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, + 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x3a, 0x1b, 0x88, 0xa0, 0x1f, 0x00, 0xe8, 0xa0, 0x1f, 0x00, 0x82, 0xe7, 0xb0, + 0x2a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x4d, 0x73, 0x67, 0x55, 0x6e, 0x6a, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbc, 0x01, 0x0a, 0x0e, 0x4d, 0x73, 0x67, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x54, 0x69, 0x70, diff --git a/app/upgrades.go b/app/upgrades.go index abf864451..7ebe3f07a 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/tellor-io/layer/app/upgrades" - v_6_1_5 "github.com/tellor-io/layer/app/upgrades/v6.1.5" + v_6_1_6 "github.com/tellor-io/layer/app/upgrades/v6.1.6" upgradetypes "cosmossdk.io/x/upgrade/types" ) @@ -13,7 +13,7 @@ var ( // `Upgrades` defines the upgrade handlers and store loaders for the application. // New upgrades should be added to this slice after they are implemented. Upgrades = []*upgrades.Upgrade{ - &v_6_1_5.Upgrade, + &v_6_1_6.Upgrade, } Forks = []upgrades.Fork{} ) @@ -21,17 +21,15 @@ var ( // setupUpgradeHandlers registers the upgrade handlers to perform custom upgrade // logic and state migrations for software upgrades. func (app *App) setupUpgradeHandlers() { - if app.UpgradeKeeper.HasHandler(v_6_1_5.UpgradeName) { - panic(fmt.Sprintf("Cannot register duplicate upgrade handler '%s'", v_6_1_5.UpgradeName)) + if app.UpgradeKeeper.HasHandler(v_6_1_6.UpgradeName) { + panic(fmt.Sprintf("Cannot register duplicate upgrade handler '%s'", v_6_1_6.UpgradeName)) } app.UpgradeKeeper.SetUpgradeHandler( - v_6_1_5.UpgradeName, - v_6_1_5.CreateUpgradeHandler( + v_6_1_6.UpgradeName, + v_6_1_6.CreateUpgradeHandler( app.ModuleManager(), app.configurator, - app.ChainID(), - app.BridgeKeeper, - app.OracleKeeper, + app.ReporterKeeper, ), ) } diff --git a/app/upgrades/v6.1.6/constants.go b/app/upgrades/v6.1.6/constants.go new file mode 100644 index 000000000..5b5b86025 --- /dev/null +++ b/app/upgrades/v6.1.6/constants.go @@ -0,0 +1,16 @@ +package v6_1_6 + +import ( + "github.com/tellor-io/layer/app/upgrades" + + store "cosmossdk.io/store/types" +) + +const ( + UpgradeName = "v6.1.6" +) + +var Upgrade = upgrades.Upgrade{ + UpgradeName: UpgradeName, + StoreUpgrades: store.StoreUpgrades{}, +} diff --git a/app/upgrades/v6.1.6/upgrade.go b/app/upgrades/v6.1.6/upgrade.go new file mode 100644 index 000000000..b4cdb32d1 --- /dev/null +++ b/app/upgrades/v6.1.6/upgrade.go @@ -0,0 +1,60 @@ +package v6_1_6 + +import ( + "context" + "fmt" + + reporterkeeper "github.com/tellor-io/layer/x/reporter/keeper" + reportertypes "github.com/tellor-io/layer/x/reporter/types" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +/* +Upgrade to v6.1.6: +- Deferred reporter switches stored in OutgoingPendingSwitches / IncomingPendingSwitchIdx + with ReporterPendingSwitchHeads for O(1) checks. +- Finalization runs at the start of ReporterStake (e.g. when a reporter submits), + not in BeginBlock. +- Pending switch targets live only in keeper collections (not on Selection). + Max pending switches per reporter is a module param (default 10). + +No custom state migration is required beyond RunMigrations: new collections and +proto fields deserialize to empty / zero for existing chains. +*/ + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + rk reporterkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + sdkCtx.Logger().Info(fmt.Sprintf("Running %s Upgrade...", UpgradeName)) + + vm, err := mm.RunMigrations(ctx, configurator, vm) + if err != nil { + return vm, err + } + + params, err := rk.Params.Get(ctx) + if err != nil { + return vm, fmt.Errorf("reporter params: %w", err) + } + if params.MaxPendingSwitchesPerReporter == 0 { + params.MaxPendingSwitchesPerReporter = reportertypes.DefaultMaxPendingSwitchesPerReporter + if err := rk.Params.Set(ctx, params); err != nil { + return vm, fmt.Errorf("set max_pending_switches_per_reporter: %w", err) + } + sdkCtx.Logger().Info( + "set reporter max_pending_switches_per_reporter", + "value", params.MaxPendingSwitchesPerReporter, + ) + } + + return vm, nil + } +} diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index dbf494cb4..c3ae3d66f 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -265,6 +265,14 @@ paths: type: string format: uint64 title: max number of validators a user can delegate too + max_pending_switches_per_reporter: + type: string + format: uint64 + description: >- + max pending reporter switches involving a reporter as + outgoing or incoming + + (each side capped separately when scheduling a switch). description: >- QueryParamsResponse is response type for the Query/Params RPC method. @@ -566,6 +574,17 @@ paths: title: >- individual_delegations contains details of each delegation (only populated when delegations_count > 1) + jailed: + type: boolean + description: >- + jailed is true while this selector is penalized from a + dispute on their reporter. + jailed_until: + type: string + format: date-time + description: >- + jailed_until is when selector dispute-jail ends (wall + clock, same semantics as reporter). title: >- FormattedSelection is a type that represents a selector's information for the SelectionsTo query @@ -1775,138 +1794,6 @@ paths: format: uint64 tags: - Query - /tellor-io/layer/oracle/get_no_stake_reports_by_qid/{query_id}: - get: - summary: Queries a list of no stake reports by query id - operationId: GetNoStakeReportsByQueryId - responses: - '200': - description: A successful response. - schema: - type: object - properties: - no_stake_reports: - type: array - items: - type: object - properties: - reporter: - type: string - title: reporter is the address of the reporter - value: - type: string - title: hex string of the response value - timestamp: - type: string - format: uint64 - title: timestamp of when the report was created - block_number: - type: string - format: uint64 - title: block number of when the report was created - title: MicroReports but with strings to return for queries - description: no_stake_reports defines the no stake reports. - pagination: - description: pagination defines the pagination in the response. - type: object - properties: - next_key: - type: string - format: byte - description: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently. It will be empty if - there are no more results. - total: - type: string - format: uint64 - title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: >- - QueryGetNoStakeReportsByQueryIdResponse is the response type for - the Query/GetNoStakeReportsByQueryId RPC method. - default: - description: An unexpected error response. - schema: - type: object - properties: - code: - type: integer - format: int32 - message: - type: string - details: - type: array - items: - type: object - properties: - '@type': - type: string - additionalProperties: {} - parameters: - - name: query_id - description: query_id defines the query id hex string. - in: path - required: true - type: string - - name: pagination.key - description: |- - key is a value returned in PageResponse.next_key to begin - querying the next page most efficiently. Only one of offset or key - should be set. - in: query - required: false - type: string - format: byte - - name: pagination.offset - description: >- - offset is a numeric offset that can be used when key is unavailable. - - It is less efficient than using key. Only one of offset or key - should - - be set. - in: query - required: false - type: string - format: uint64 - - name: pagination.limit - description: >- - limit is the total number of results to be returned in the result - page. - - If left empty it will default to a value to be set by each app. - in: query - required: false - type: string - format: uint64 - - name: pagination.count_total - description: >- - count_total is set to true to indicate that the result set should - include - - a count of the total number of items available for pagination in - UIs. - - count_total is only respected when offset is used. It is ignored - when key - - is set. - in: query - required: false - type: boolean - - name: pagination.reverse - description: >- - reverse is set to true if results are to be returned in the - descending order. - in: query - required: false - type: boolean - tags: - - Query /tellor-io/layer/oracle/get_query/{query_id}/{id}: get: summary: Queries a query by query id and id @@ -2086,138 +1973,6 @@ paths: type: string tags: - Query - /tellor-io/layer/oracle/get_reporters_no_stake_reports/{reporter}: - get: - summary: Queries a list of no stake reports by reporter - operationId: GetReportersNoStakeReports - responses: - '200': - description: A successful response. - schema: - type: object - properties: - no_stake_reports: - type: array - items: - type: object - properties: - reporter: - type: string - title: reporter is the address of the reporter - value: - type: string - title: hex string of the response value - timestamp: - type: string - format: uint64 - title: timestamp of when the report was created - block_number: - type: string - format: uint64 - title: block number of when the report was created - title: MicroReports but with strings to return for queries - description: no_stake_reports defines the no stake reports. - pagination: - description: pagination defines the pagination in the response. - type: object - properties: - next_key: - type: string - format: byte - description: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently. It will be empty if - there are no more results. - total: - type: string - format: uint64 - title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: >- - QueryGetReportersNoStakeReportsResponse is the response type for - the Query/GetReportersNoStakeReports RPC method. - default: - description: An unexpected error response. - schema: - type: object - properties: - code: - type: integer - format: int32 - message: - type: string - details: - type: array - items: - type: object - properties: - '@type': - type: string - additionalProperties: {} - parameters: - - name: reporter - description: reporter defines the reporter address. - in: path - required: true - type: string - - name: pagination.key - description: |- - key is a value returned in PageResponse.next_key to begin - querying the next page most efficiently. Only one of offset or key - should be set. - in: query - required: false - type: string - format: byte - - name: pagination.offset - description: >- - offset is a numeric offset that can be used when key is unavailable. - - It is less efficient than using key. Only one of offset or key - should - - be set. - in: query - required: false - type: string - format: uint64 - - name: pagination.limit - description: >- - limit is the total number of results to be returned in the result - page. - - If left empty it will default to a value to be set by each app. - in: query - required: false - type: string - format: uint64 - - name: pagination.count_total - description: >- - count_total is set to true to indicate that the result set should - include - - a count of the total number of items available for pagination in - UIs. - - count_total is only respected when offset is used. It is ignored - when key - - is set. - in: query - required: false - type: boolean - - name: pagination.reverse - description: >- - reverse is set to true if results are to be returned in the - descending order. - in: query - required: false - type: boolean - tags: - - Query /tellor-io/layer/oracle/get_reports_by_aggregate/{query_id}/{timestamp}: get: summary: Queries reports by aggregate by query id and timestamp @@ -2565,7 +2320,7 @@ paths: schema: type: object properties: - queries: + active_queries: type: array items: type: object @@ -2602,7 +2357,48 @@ paths: title: >- QueryMetaButString is QueryMeta but with the query_data as a string for query display purposes - title: querymeta but string query data + title: querymeta but string query data for active queries + expired_queries: + type: array + items: + type: object + properties: + id: + type: string + format: uint64 + title: >- + unique id of the query that changes after query's + lifecycle ends + amount: + type: string + title: amount of tokens that was tipped + expiration: + type: string + format: uint64 + title: expiration time of the query + registry_spec_block_window: + type: string + format: uint64 + title: timeframe of the query according to the data spec + has_revealed_reports: + type: boolean + title: indicates whether query has revealed reports + query_data: + type: string + title: decoded hex string of the query_data + query_type: + type: string + title: string identifier of the data spec + cycle_list: + type: boolean + title: bool cycle list query + title: >- + QueryMetaButString is QueryMeta but with the query_data as a + string for query display purposes + title: querymeta but string query data for expired queries with tips + available_tips_total: + type: string + description: available_tips_total defines the total available tips. pagination: description: pagination defines the pagination in the response. type: object diff --git a/e2e/dispute_test.go b/e2e/dispute_test.go index a0f0e8690..e34caa60a 100644 --- a/e2e/dispute_test.go +++ b/e2e/dispute_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tellor-io/layer/e2e" layerutil "github.com/tellor-io/layer/testutil" + layertypes "github.com/tellor-io/layer/types" registrytypes "github.com/tellor-io/layer/x/registry/types" "cosmossdk.io/math" @@ -47,6 +48,14 @@ const ( notFromBond = "false" ) +// minorDisputeFeeFromReportPower matches x/dispute keeper GetDisputeFee for minor disputes (5% of report bond). +func minorDisputeFeeFromReportPower(reportPower uint64) math.Int { + stake := layertypes.PowerReduction.MulRaw(int64(reportPower)) + stakeDec := math.LegacyNewDecFromInt(stake) + feeDec := stakeDec.Mul(math.LegacyNewDec(5)).Quo(math.LegacyNewDec(100)) + return feeDec.TruncateInt() +} + type QueryData struct { QueryData string QueryID string @@ -1045,17 +1054,17 @@ func TestReportDelegateMoreMajorDispute(t *testing.T) { require.Error(err) fmt.Println("TX HASH (user1 tries to remove self as selector): ", txHash) - // user1 tries to become a selector, cant because of reporting in the last 21 days + // user1 switches reporter to user0 (solo reporter: no 21-day block; pending switch until finalize) txHash, err = val1.ExecTx(ctx, user1Addr, "reporter", "switch-reporter", user0Addr, "--keyring-dir", val1.HomeDir()) - require.Error(err) - fmt.Println("TX HASH (user1 tries to become a selector): ", txHash) + require.NoError(err) + fmt.Println("TX HASH (user1 switches reporter to user0): ", txHash) // check reporter module res, _, err = e2e.QueryWithTimeout(ctx, val1, "reporter", "reporters") require.NoError(err) err = json.Unmarshal(res, &reportersRes) require.NoError(err) - require.Equal(len(reportersRes.Reporters), numReporters+1) // 2 reporters + 1 validator reporter + require.Equal(len(reportersRes.Reporters), numReporters) // 2 reporters (the third reporter was deleted ) fmt.Println("reportersRes: ", reportersRes) // user1 redelegates to val2 @@ -1261,17 +1270,7 @@ func TestEscalatingDispute(t *testing.T) { require.Equal(disputes.Disputes[0].Metadata.FeeTotal, "10000000") // 10 * 1e6 is 1% of 1000 fmt.Println("open dispute: ", disputes.Disputes[0]) - // try to open minor dispute on same report, errors with cannot jail already jailed reporter - txHash, err = val1.Node.ExecTx(ctx, user0Addr, "dispute", "propose-dispute", reports.MicroReports[0].Reporter, reports.MicroReports[0].MetaId, reports.MicroReports[0].QueryID, "minor", "1000000000loya", "true", "--keyring-dir", val1.Node.HomeDir(), "--gas", "500000", "--fees", "50loya") - require.Error(err) - fmt.Println("TX HASH (user0 opens minor dispute): ", txHash) - - // user1 unjails reporter - txHash, err = val1.Node.ExecTx(ctx, user1Addr, "reporter", "unjail-reporter", "--keyring-dir", val1.Node.HomeDir()) - require.NoError(err) - fmt.Println("TX HASH (user1 unjails reporter): ", txHash) - - // user0 opens minor dispute on same report + // user0 opens minor dispute on same report (jailed reporter can be jailed again; idempotent jail) txHash, err = val1.Node.ExecTx(ctx, user0Addr, "dispute", "propose-dispute", reports.MicroReports[0].Reporter, reports.MicroReports[0].MetaId, reports.MicroReports[0].QueryID, "minor", "1000000000loya", "true", "--keyring-dir", val1.Node.HomeDir(), "--gas", "500000", "--fees", "50loya") require.NoError(err) fmt.Println("TX HASH (user0 opens minor dispute): ", txHash) @@ -1894,7 +1893,7 @@ func TestEverybodyDisputed_NotConsensus_Consensus(t *testing.T) { // unjail reporters for i, usr := range userReports { - txHash, err = val1.Node.ExecTx(ctx, usr.UserReport.MicroReports[0].Reporter, "reporter", "unjail-reporter", "--keyring-dir", val1.Node.HomeDir()) + txHash, err = val1.Node.ExecTx(ctx, usr.UserReport.MicroReports[0].Reporter, "reporter", "unjail-reporter", usr.UserReport.MicroReports[0].Reporter, "--keyring-dir", val1.Node.HomeDir()) require.NoError(err) fmt.Println("TX HASH (user", i, "unjails reporter): ", txHash) } @@ -2376,8 +2375,14 @@ func TestReporterShuffleAndDispute(t *testing.T) { cosmos.SetSDKConfig("tellor") - // Use standard configuration - chain, ic, ctx := e2e.SetupChain(t, 2, 0) + // Short SpotPrice commitment window so self-demotion can proceed after a few blocks. + config := e2e.DefaultSetupConfig() + config.NumValidators = 2 + config.NumFullNodes = 0 + config.ModifyGenesis = append(config.ModifyGenesis, + cosmos.NewGenesisKV("app_state.registry.dataspec.0.report_block_window", "5"), + ) + chain, ic, ctx := e2e.SetupChainWithCustomConfig(t, config) defer ic.Close() // Get validators using the helper @@ -2431,10 +2436,13 @@ func TestReporterShuffleAndDispute(t *testing.T) { fmt.Println("reports from val2: ", reportsRes) require.NotEmpty(reportsRes.MicroReports, "val2 should have reports after aggregation") - // val2 tries to become a selector for val1 instead of a reporter, shouldnt be allowed because of reporting in the last 21 days + // Wait until val2's max open commitment height has passed before self-demotion. + require.NoError(testutil.WaitForBlocks(ctx, 8, val1.Node)) + + // val2 switches reporter to val1 (solo reporter: pending switch; selector stays on val2 until finalize) txHash, err := val2.Node.ExecTx(ctx, val2.AccAddr, "reporter", "switch-reporter", val1.AccAddr, "--keyring-dir", val2.Node.HomeDir()) - require.Error(err) - fmt.Println("TX HASH (val2 fails to become a selector): ", txHash) + require.NoError(err) + fmt.Println("TX HASH (val2 switches reporter to val1): ", txHash) // verify val2 is still a selector for themselves (default reporter state) res, _, err := e2e.QueryWithTimeout(ctx, val1.Node, "reporter", "selector-reporter", val2.AccAddr) @@ -2446,10 +2454,10 @@ func TestReporterShuffleAndDispute(t *testing.T) { require.Equal(selectorRes.Reporter, val2.AccAddr) // make third party user to dispute + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) keyname := "user1" fundAmt := math.NewInt(100_000 * 1e6) user := interchaintest.GetAndFundTestUsers(t, ctx, keyname, fundAmt, chain)[0] - fmt.Println("user: ", user) userAddr := user.FormattedAddress() // user1 disputes val1's report @@ -2780,3 +2788,212 @@ func TestGroupPowers(t *testing.T) { require.NoError(err) require.Equal(disputes.Disputes[0].Metadata.DisputeStatus, "DISPUTE_STATUS_RESOLVED") // executed } + +// TestReporterSwitchDisputeSelectorStakeE2E exercises fast SpotPrice switch finalization, then a minor +// dispute on reporter A's pre-switch report. Selector oracle power on B must exclude/include around jail. +// +// 1. Two validators + external selector on val0; SpotPrice report on val0 (record meta/query) +// 2. Baseline: val0 report power includes selector; val1 reporter query power does not +// 3. Selector switch-reporter val1; val1 SpotPrice submit-value finalizes switch (report power + reporter query) +// 4. Full minor dispute fee on val0's pre-switch report → voting + selector dispute lock +// 5. While dispute-locked: val1 reporter query and report power exclude selector vs step 3 +// 6. Wall-clock sleep 601s (minor jail duration is 600s of chain block time; blocks must advance) +// 7. val1 reports again; reporter query and report power regain selector stake +func TestReporterSwitchDisputeSelectorStakeE2E(t *testing.T) { + require := require.New(t) + + cosmos.SetSDKConfig("tellor") + + chain, ic, ctx := e2e.SetupChain(t, 2, 0) + defer ic.Close() + + validators, err := e2e.GetValidators(ctx, chain) + require.NoError(err) + val0, val1 := validators[0], validators[1] + + require.NoError(e2e.TurnOnMinting(ctx, chain, val0.Node)) + require.NoError(testutil.WaitForBlocks(ctx, 7, val0.Node)) + + for i, val := range validators { + _, err := val.Node.ExecTx(ctx, val.AccAddr, "reporter", "create-reporter", commissRate, "1000000", fmt.Sprintf("switch_e2e_%d", i), "--keyring-dir", val.Node.HomeDir()) + require.NoError(err) + } + + fundAmt := math.NewInt(5_000 * 1e6) + delegateAmt := sdk.NewCoin("loya", math.NewInt(1000*1e6)) + selector := interchaintest.GetAndFundTestUsers(t, ctx, "switch_selector", fundAmt, chain)[0] + selectorAddr := selector.FormattedAddress() + + _, err = val0.Node.ExecTx(ctx, selectorAddr, "staking", "delegate", val0.ValAddr, delegateAmt.String(), "--keyring-dir", val0.Node.HomeDir(), "--fees", "10loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val0.Node)) + + _, err = val0.Node.ExecTx(ctx, selectorAddr, "reporter", "select-reporter", val0.AccAddr, "--keyring-dir", val0.Node.HomeDir(), "--fees", "5loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val0.Node)) + + spotValue := layerutil.EncodeValue(50000.99) + tip := sdk.NewCoin("loya", math.NewInt(1*1e6)) + + // SpotPrice on val0 (reporter A) — report disputed later. + require.NoError(testutil.WaitForBlocks(ctx, 1, val0.Node)) + _, _, err = val0.Node.Exec(ctx, val0.Node.TxCommand(val0.AccAddr, "oracle", "tip", bnbQData, tip.String(), "--fees", "25loya", "--keyring-dir", val0.Node.HomeDir()), val0.Node.Chain.Config().Env) + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 1, val0.Node)) + _, err = val0.Node.ExecTx(ctx, val0.AccAddr, "oracle", "submit-value", bnbQData, spotValue, "--keyring-dir", val0.Node.HomeDir(), "--gas", "500000", "--fees", "25loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val0.Node)) + + reportsA, _, err := e2e.QueryWithTimeout(ctx, val0.Node, "oracle", "get-reportsby-reporter", val0.AccAddr, "--page-limit", "1") + require.NoError(err) + var reportsARes e2e.QueryMicroReportsResponse + require.NoError(json.Unmarshal(reportsA, &reportsARes)) + require.NotEmpty(reportsARes.MicroReports) + disputedReport := reportsARes.MicroReports[0] + require.Equal("SpotPrice", disputedReport.QueryType) + powerA0, err := strconv.ParseUint(disputedReport.Power, 10, 64) + require.NoError(err) + + stakeBBase, err := e2e.QueryReporterPower(ctx, val1.Node, val1.AccAddr) + require.NoError(err) + require.Greater(powerA0, stakeBBase, "val0 report power should include selector stake; val1 reporter query should not") + + _, err = val0.Node.ExecTx(ctx, selectorAddr, "reporter", "switch-reporter", val1.AccAddr, "--keyring-dir", val0.Node.HomeDir(), "--fees", "5loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val0.Node)) + + // Pending switch stays on val0 until ReporterStake runs after height > open commitment + // (SpotPrice report_block_window is 2). val1's submit-value triggers finalize. + resPending, _, err := e2e.QueryWithTimeout(ctx, val0.Node, "reporter", "selector-reporter", selectorAddr) + require.NoError(err) + var selectorPending e2e.QuerySelectorReporterResponse + require.NoError(json.Unmarshal(resPending, &selectorPending)) + require.Equal(val0.AccAddr, selectorPending.Reporter, + "switch must not be finalized before val1 SpotPrice report (selector still on outgoing reporter)") + + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) + _, _, err = val1.Node.Exec(ctx, val1.Node.TxCommand(val1.AccAddr, "oracle", "tip", xrpQData, tip.String(), "--fees", "25loya", "--keyring-dir", val1.Node.HomeDir()), val1.Node.Chain.Config().Env) + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) + _, err = val1.Node.ExecTx(ctx, val1.AccAddr, "oracle", "submit-value", xrpQData, spotValue, "--keyring-dir", val1.Node.HomeDir(), "--gas", "500000", "--fees", "25loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val1.Node)) + + res, _, err := e2e.QueryWithTimeout(ctx, val0.Node, "reporter", "selector-reporter", selectorAddr) + require.NoError(err) + var selectorRes e2e.QuerySelectorReporterResponse + require.NoError(json.Unmarshal(res, &selectorRes)) + require.Equal(val1.AccAddr, selectorRes.Reporter, + "switch must finalize on val1 submit-value after SpotPrice open commitment") + require.NotEqual(val0.AccAddr, selectorRes.Reporter, + "selector must not remain on val0 after finalize") + + stakeBAfterSwitch, err := e2e.QueryReporterPower(ctx, val1.Node, val1.AccAddr) + require.NoError(err) + require.Greater(stakeBAfterSwitch, stakeBBase, "val1 reporter query should include selector stake after switch finalize") + + reportsB1, _, err := e2e.QueryWithTimeout(ctx, val1.Node, "oracle", "get-reportsby-reporter", val1.AccAddr, "--page-limit", "5") + require.NoError(err) + var reportsB1Res e2e.QueryMicroReportsResponse + require.NoError(json.Unmarshal(reportsB1, &reportsB1Res)) + var switchReport *e2e.MicroReport + for i := range reportsB1Res.MicroReports { + if reportsB1Res.MicroReports[i].QueryID == xrpQId { + switchReport = &reportsB1Res.MicroReports[i] + break + } + } + require.NotNil(switchReport, "val1 XRP SpotPrice report must exist after switch finalize") + powerBSwitchReport, err := strconv.ParseUint(switchReport.Power, 10, 64) + require.NoError(err) + require.Equal(stakeBAfterSwitch, powerBSwitchReport, + "switch-finalize report power must match reporter stake query") + + // Pay the full minor fee in one propose (triggers jail + voting), same as integration proposeFullMinorDispute. + minorFeeAmt := minorDisputeFeeFromReportPower(powerA0) + minorFeeCoin := sdk.NewCoin("loya", minorFeeAmt) + disputerFund := minorFeeAmt.Add(math.NewInt(100_000 * 1e6)) // fee + gas headroom + disputer := interchaintest.GetAndFundTestUsers(t, ctx, "disputer", disputerFund, chain)[0] + disputerAddr := disputer.FormattedAddress() + _, err = val0.Node.ExecTx(ctx, disputerAddr, "dispute", "propose-dispute", + disputedReport.Reporter, disputedReport.MetaId, disputedReport.QueryID, + "minor", minorFeeCoin.String(), notFromBond, + "--keyring-dir", val0.Node.HomeDir(), "--gas", "500000", "--fees", "50loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val0.Node)) + + disRes, _, err := e2e.QueryWithTimeout(ctx, val0.Node, "dispute", "disputes") + require.NoError(err) + var disputes e2e.Disputes2 + require.NoError(json.Unmarshal(disRes, &disputes)) + require.Len(disputes.Disputes, 1) + disputeMeta := disputes.Disputes[0].Metadata + require.Equal("DISPUTE_STATUS_VOTING", disputeMeta.DisputeStatus, "full minor fee must jail reporter and open voting") + require.Equal(minorFeeAmt.String(), disputeMeta.FeeTotal) + require.Equal(disputeMeta.FeeTotal, disputeMeta.SlashAmount) + require.Equal(disputeMeta.DisputeFee, disputeMeta.FeeTotal) + + // val1 reports again while selector is dispute-locked. + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) + _, _, err = val1.Node.Exec(ctx, val1.Node.TxCommand(val1.AccAddr, "oracle", "tip", solQData, tip.String(), "--fees", "25loya", "--keyring-dir", val1.Node.HomeDir()), val1.Node.Chain.Config().Env) + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) + _, err = val1.Node.ExecTx(ctx, val1.AccAddr, "oracle", "submit-value", solQData, spotValue, "--keyring-dir", val1.Node.HomeDir(), "--gas", "500000", "--fees", "25loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val1.Node)) + + stakeBLocked, err := e2e.QueryReporterPower(ctx, val1.Node, val1.AccAddr) + require.NoError(err) + require.Equal(stakeBBase, stakeBLocked, "dispute-locked selector must not count toward val1 reporter query power") + + reportsBLocked, _, err := e2e.QueryWithTimeout(ctx, val1.Node, "oracle", "get-reportsby-reporter", val1.AccAddr, "--page-limit", "10") + require.NoError(err) + var reportsBLockedRes e2e.QueryMicroReportsResponse + require.NoError(json.Unmarshal(reportsBLocked, &reportsBLockedRes)) + var lockedReport *e2e.MicroReport + for i := range reportsBLockedRes.MicroReports { + if reportsBLockedRes.MicroReports[i].QueryID == solQId { + lockedReport = &reportsBLockedRes.MicroReports[i] + break + } + } + require.NotNil(lockedReport, "val1 SOL SpotPrice report must exist while dispute-locked") + powerBLocked, err := strconv.ParseUint(lockedReport.Power, 10, 64) + require.NoError(err) + require.Equal(stakeBLocked, powerBLocked, "dispute-locked report power must match reporter stake query") + require.Less(powerBLocked, powerBSwitchReport, "dispute-locked selector must not count toward val1 report power") + + // Minor jail uses 600s wall clock; integration tests advance BlockTime, e2e uses real sleep. + time.Sleep(601 * time.Second) + + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) + _, _, err = val1.Node.Exec(ctx, val1.Node.TxCommand(val1.AccAddr, "oracle", "tip", dotQData, tip.String(), "--fees", "25loya", "--keyring-dir", val1.Node.HomeDir()), val1.Node.Chain.Config().Env) + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 1, val1.Node)) + _, err = val1.Node.ExecTx(ctx, val1.AccAddr, "oracle", "submit-value", dotQData, spotValue, "--keyring-dir", val1.Node.HomeDir(), "--gas", "500000", "--fees", "25loya") + require.NoError(err) + require.NoError(testutil.WaitForBlocks(ctx, 2, val1.Node)) + + stakeBUnlocked, err := e2e.QueryReporterPower(ctx, val1.Node, val1.AccAddr) + require.NoError(err) + require.Greater(stakeBUnlocked, stakeBBase, "val1 reporter query should regain selector stake after dispute lock expiry") + require.Greater(stakeBUnlocked, stakeBLocked) + + reportsBUnlocked, _, err := e2e.QueryWithTimeout(ctx, val1.Node, "oracle", "get-reportsby-reporter", val1.AccAddr, "--page-limit", "15") + require.NoError(err) + var reportsBUnlockedRes e2e.QueryMicroReportsResponse + require.NoError(json.Unmarshal(reportsBUnlocked, &reportsBUnlockedRes)) + var unlockedReport *e2e.MicroReport + for i := range reportsBUnlockedRes.MicroReports { + if reportsBUnlockedRes.MicroReports[i].QueryID == dotQId { + unlockedReport = &reportsBUnlockedRes.MicroReports[i] + break + } + } + require.NotNil(unlockedReport, "val1 DOT SpotPrice report must exist after dispute lock expiry") + powerBUnlocked, err := strconv.ParseUint(unlockedReport.Power, 10, 64) + require.NoError(err) + require.Equal(stakeBUnlocked, powerBUnlocked, + "post-unlock report power must match reporter stake query") + require.Greater(powerBUnlocked, powerBLocked, "val1 report power should regain selector stake after dispute lock expiry") +} diff --git a/e2e/stake_cache_test.go b/e2e/stake_cache_test.go index 2d6d3e999..c1157b306 100644 --- a/e2e/stake_cache_test.go +++ b/e2e/stake_cache_test.go @@ -25,7 +25,11 @@ import ( const ( stakeCacheCommissRate = "0.1" moniker = "reporter_0" - stakeCacheTxFee = "500loya" + // Shared fee for stake-cache txs; keep this high enough to avoid fee-related flakes. + stakeCacheTxFee = "7loya" + // submit-value with stake recalc (e.g. after switch-reporter) can exceed the 200k default gas. + stakeCacheSubmitGas = "500000" + stakeCacheSubmitFees = "25loya" // must cover 500k * min gas price (~13.75 loya with adjustment) ) func stakeCacheSetupConfig() e2e.SetupConfig { @@ -150,7 +154,7 @@ func TestStakingHooksTriggered(t *testing.T) { user := interchaintest.GetAndFundTestUsers(t, ctx, "selector", fundAmt, chain)[0] fmt.Println("=== Step 2: Selector address:", user.FormattedAddress()) - txHash, err = validators[0].Node.ExecTx(ctx, user.FormattedAddress(), "staking", "delegate", validators[0].ValAddr, initialDelegate.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--fees", stakeCacheTxFee) + txHash, err = validators[0].Node.ExecTx(ctx, user.FormattedAddress(), "staking", "delegate", validators[0].ValAddr, initialDelegate.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--fees", "5loya") require.NoError(err) fmt.Println("=== Step 2: Initial delegation txHash:", txHash) require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) @@ -179,7 +183,8 @@ func TestStakingHooksTriggered(t *testing.T) { firstQueryMetaID := currentCycleList.QueryMeta.Id value := layerutil.EncodeValue(500.0) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) // Get first report power @@ -210,7 +215,8 @@ func TestStakingHooksTriggered(t *testing.T) { // and ReporterStake will recalculate instead of using cache currentCycleList = waitForStakeCacheCycleListQuery(t, ctx, validators[0].Node, firstQueryMetaID) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) // Get second report power @@ -261,7 +267,9 @@ func TestStakeCacheValSetUpdate(t *testing.T) { // Validator 0 becomes a reporter minStakeAmt := "1000000" - txHash, err := validators[0].Node.ExecTx(ctx, validators[0].AccAddr, "reporter", "create-reporter", stakeCacheCommissRate, minStakeAmt, moniker, "--keyring-dir", validators[0].Node.HomeDir()) + highGas := stakeCacheSubmitGas + highFees := stakeCacheSubmitFees + txHash, err := validators[0].Node.ExecTx(ctx, validators[0].AccAddr, "reporter", "create-reporter", stakeCacheCommissRate, minStakeAmt, moniker, "--keyring-dir", validators[0].Node.HomeDir(), "--gas", highGas, "--fees", highFees) require.NoError(err) fmt.Println("TX HASH (validator 0 becomes reporter):", txHash) @@ -271,7 +279,8 @@ func TestStakeCacheValSetUpdate(t *testing.T) { // First report value := layerutil.EncodeValue(100.0) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) fmt.Println("First report submitted") require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) @@ -291,18 +300,18 @@ func TestStakeCacheValSetUpdate(t *testing.T) { })) require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) - txHash, err = validators[0].Node.ExecTx(ctx, validators[0].AccAddr, "staking", "delegate", validators[0].ValAddr, delegateAmt.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--gas", "500000", "--fees", stakeCacheTxFee) + txHash, err = validators[0].Node.ExecTx(ctx, validators[0].AccAddr, "staking", "delegate", validators[0].ValAddr, delegateAmt.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--gas", highGas, "--fees", highFees) require.NoError(err) fmt.Println("TX HASH (validator 0 self-delegates more):", txHash) tooMuchSelfDelegate := sdk.NewCoin("loya", math.NewInt(2_000_000*1e6)) - _, err = validators[0].Node.ExecTx(ctx, validators[0].AccAddr, "staking", "delegate", validators[0].ValAddr, tooMuchSelfDelegate.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--gas", "500000", "--fees", stakeCacheTxFee) + _, err = validators[0].Node.ExecTx(ctx, validators[0].AccAddr, "staking", "delegate", validators[0].ValAddr, tooMuchSelfDelegate.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--gas", highGas, "--fees", highFees) require.Error(err) require.ErrorContains(err, "total stake increase exceeds the allowed 5% threshold within a twelve-hour period") overLimitDelegator := interchaintest.GetAndFundTestUsers(t, ctx, "stake-share-over-limit", math.NewInt(10_000_000*1e6), chain)[0] tooMuchDelegatorStake := sdk.NewCoin("loya", math.NewInt(9_000_000*1e6)) - _, err = validators[0].Node.ExecTx(ctx, overLimitDelegator.FormattedAddress(), "staking", "delegate", validators[0].ValAddr, tooMuchDelegatorStake.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--gas", "500000", "--fees", stakeCacheTxFee) + _, err = validators[0].Node.ExecTx(ctx, overLimitDelegator.FormattedAddress(), "staking", "delegate", validators[0].ValAddr, tooMuchDelegatorStake.String(), "--keyring-dir", validators[0].Node.HomeDir(), "--gas", highGas, "--fees", highFees) require.Error(err) require.ErrorContains(err, "total stake increase exceeds the allowed 5% threshold within a twelve-hour period") @@ -311,7 +320,8 @@ func TestStakeCacheValSetUpdate(t *testing.T) { // Second report - should trigger recalculation due to validator set update currentCycleList = waitForStakeCacheCycleListQuery(t, ctx, validators[0].Node, firstQueryMetaID) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) fmt.Println("Second report submitted") require.NoError(testutil.WaitForBlocks(ctx, 3, validators[0].Node)) @@ -368,7 +378,8 @@ func TestStakeCacheSelectorJoin(t *testing.T) { require.NoError(json.Unmarshal(currentCycleListRes, ¤tCycleList)) value := layerutil.EncodeValue(200.0) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) fmt.Println("First report submitted") require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) @@ -398,7 +409,8 @@ func TestStakeCacheSelectorJoin(t *testing.T) { require.NoError(err) require.NoError(json.Unmarshal(currentCycleListRes, ¤tCycleList)) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) fmt.Println("Second report submitted") require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) @@ -473,7 +485,8 @@ func TestStakeCacheSelectorSwitch(t *testing.T) { value := layerutil.EncodeValue(300.0) for i := range validators { - submitStakeCacheValue(t, ctx, validators[i].Node, currentCycleList.QueryData, value) + _, _, err = validators[i].Node.Exec(ctx, validators[i].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[i].Node.HomeDir()), validators[i].Node.Chain.Config().Env) + require.NoError(err) fmt.Printf("Validator %d first report submitted\n", i) } @@ -511,7 +524,8 @@ func TestStakeCacheSelectorSwitch(t *testing.T) { currentCycleList = waitForStakeCacheCycleListQuery(t, ctx, validators[0].Node, firstQueryMetaID) for i := range validators { - submitStakeCacheValue(t, ctx, validators[i].Node, currentCycleList.QueryData, value) + _, _, err = validators[i].Node.Exec(ctx, validators[i].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[i].Node.HomeDir()), validators[i].Node.Chain.Config().Env) + require.NoError(err) fmt.Printf("Validator %d second report submitted\n", i) } @@ -537,14 +551,12 @@ func TestStakeCacheSelectorSwitch(t *testing.T) { fmt.Printf("Validator %d power after switch: %d\n", i, power) } - // After switch: reporter 0 should lose selector's stake. - // Reporter 1 does NOT gain the selector's stake yet because the selector is locked - // for the unbonding period after switching reporters (LockedUntilTime is set in SwitchReporter). - // GetReporterStake skips selectors whose LockedUntilTime is after the current block time. + // After switch: reporter 0 loses selector stake immediately (pending switch away). + // Reporter 1 gains stake on the next submit when unlock_block < height (often 0 after cyclelist). fmt.Printf("Reporter 0: %d -> %d\n", reporter0PowerBefore, reporter0PowerAfter) fmt.Printf("Reporter 1: %d -> %d\n", reporter1PowerBefore, reporter1PowerAfter) require.Less(reporter0PowerAfter, reporter0PowerBefore, "Reporter 0 should lose power after selector switches away") - require.Equal(reporter1PowerAfter, reporter1PowerBefore, "Reporter 1 should not gain power yet (selector is locked for unbonding period)") + require.Greater(reporter1PowerAfter, reporter1PowerBefore, "Reporter 1 should gain power after pending switch finalizes") } // TestStakeCacheDelegationChange tests that reporter stake is recalculated after delegation change @@ -598,7 +610,8 @@ func TestStakeCacheDelegationChange(t *testing.T) { require.NoError(json.Unmarshal(currentCycleListRes, ¤tCycleList)) value := layerutil.EncodeValue(400.0) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) fmt.Println("First report submitted") require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) @@ -629,7 +642,8 @@ func TestStakeCacheDelegationChange(t *testing.T) { require.NoError(err) require.NoError(json.Unmarshal(currentCycleListRes, ¤tCycleList)) - submitStakeCacheValue(t, ctx, validators[0].Node, currentCycleList.QueryData, value) + _, _, err = validators[0].Node.Exec(ctx, validators[0].Node.TxCommand("validator", "oracle", "submit-value", currentCycleList.QueryData, value, "--gas", stakeCacheSubmitGas, "--fees", stakeCacheSubmitFees, "--keyring-dir", validators[0].Node.HomeDir()), validators[0].Node.Chain.Config().Env) + require.NoError(err) fmt.Println("Second report submitted") require.NoError(testutil.WaitForBlocks(ctx, 2, validators[0].Node)) diff --git a/e2e/upgrade_test.go b/e2e/upgrade_test.go index 2d15e4f24..b9e15d110 100644 --- a/e2e/upgrade_test.go +++ b/e2e/upgrade_test.go @@ -29,7 +29,7 @@ const ( ) func TestLayerUpgrade(t *testing.T) { - ChainUpgradeTest(t, "layer", "layer", "local", "v6.1.5") + ChainUpgradeTest(t, "layer", "layer", "local", "v6.1.6") } func ChainUpgradeTest(t *testing.T, chainName, upgradeContainerRepo, upgradeVersion, upgradeName string) { diff --git a/e2e/utils.go b/e2e/utils.go index 8f257d7b5..0f6b5e17d 100644 --- a/e2e/utils.go +++ b/e2e/utils.go @@ -9,6 +9,7 @@ import ( "math/big" "os" "path/filepath" + "strconv" "testing" "time" @@ -359,6 +360,30 @@ type QueryReportersResponse struct { } `json:"pagination"` } +type QueryReporterResponse struct { + Reporter *Reporter `json:"reporter"` +} + +// QueryReporterPower returns reporting power (bonded stake / PowerReduction) for a reporter. +func QueryReporterPower(ctx context.Context, node *cosmos.ChainNode, reporterAddr string) (uint64, error) { + res, _, err := QueryWithTimeout(ctx, node, "reporter", "reporter", reporterAddr) + if err != nil { + return 0, err + } + var repRes QueryReporterResponse + if err := json.Unmarshal(res, &repRes); err != nil { + return 0, err + } + if repRes.Reporter == nil || repRes.Reporter.Power == "" { + return 0, fmt.Errorf("reporter %s: missing power in query response", reporterAddr) + } + power, err := strconv.ParseUint(repRes.Reporter.Power, 10, 64) + if err != nil { + return 0, fmt.Errorf("reporter %s: parse power %q: %w", reporterAddr, repRes.Reporter.Power, err) + } + return power, nil +} + type ReportersResponse struct { Reporters []*Reporter `json:"reporters"` } diff --git a/proto/layer/reporter/params.proto b/proto/layer/reporter/params.proto index 4b355dcf4..a3d02936b 100644 --- a/proto/layer/reporter/params.proto +++ b/proto/layer/reporter/params.proto @@ -31,6 +31,9 @@ message Params { uint64 max_selectors = 3; // max number of validators a user can delegate too uint64 max_num_of_delegations = 4; + // max pending reporter switches involving a reporter as outgoing or incoming + // (each side capped separately when scheduling a switch). + uint64 max_pending_switches_per_reporter = 5; } message StakeTracker { diff --git a/proto/layer/reporter/selection.proto b/proto/layer/reporter/selection.proto index 445a14737..121c538ce 100644 --- a/proto/layer/reporter/selection.proto +++ b/proto/layer/reporter/selection.proto @@ -12,14 +12,38 @@ option go_package = "github.com/tellor-io/layer/x/reporter/types"; message Selection { // reporter is the address of the reporter being delegated to bytes reporter = 1; - // locked_until_time is the time until which the tokens are locked before they - // can be used for reporting again + // locked_until_time — non-dispute / legacy stake exclusion only (e.g. pre-upgrade switch locks). google.protobuf.Timestamp locked_until_time = 2 [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; // delegations_count is the number of delegations to the reporter uint64 delegations_count = 3; + // switch_out_locked_until_block is the block height until which this selector + // cannot initiate another reporter switch while a handoff is incomplete: it + // stores the oracle snapshot (max open query expiration for the outgoing + // reporter at schedule time). Cleared when the pending switch is finalized. + uint64 switch_out_locked_until_block = 4; + // dispute_locked_until — dispute jail only; max on set; never copied to locked_until_time. + google.protobuf.Timestamp dispute_locked_until = 5 [ + (gogoproto.nullable) = false, + (gogoproto.stdtime) = true + ]; +} + +// Pending switch keyed by (outgoing_reporter, selector) in keeper collections. +message PendingSwitchEntry { + bytes to_reporter = 1; + uint64 unlock_block = 2; +} + +// Single-reporter summary for O(1) checks in ReporterStake: one Get per reporter +// to see if any pending switch involving this reporter may be ready at height. +message ReporterPendingSwitchHead { + uint32 outgoing_count = 1; + uint64 outgoing_min_unlock = 2; + uint32 incoming_count = 3; + uint64 incoming_min_unlock = 4; } // IndividualDelegation represents a single delegation to a validator @@ -54,6 +78,11 @@ message FormattedSelection { ]; // individual_delegations contains details of each delegation (only populated when delegations_count > 1) repeated IndividualDelegation individual_delegations = 5; + // dispute_locked_until — dispute jail only (query surface). + google.protobuf.Timestamp dispute_locked_until = 6 [ + (gogoproto.nullable) = false, + (gogoproto.stdtime) = true + ]; } message SelectorShare { diff --git a/proto/layer/reporter/tx.proto b/proto/layer/reporter/tx.proto index ef8db8677..b02a5448a 100644 --- a/proto/layer/reporter/tx.proto +++ b/proto/layer/reporter/tx.proto @@ -128,10 +128,13 @@ message MsgRemoveSelectorResponse {} // MsgUnjailReporter defines the Msg/UnjailReporter request type. message MsgUnjailReporter { - option (cosmos.msg.v1.signer) = "reporter_address"; + option (cosmos.msg.v1.signer) = "signer_address"; option (gogoproto.equal) = false; option (gogoproto.goproto_getters) = false; - string reporter_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // signer_address is the transaction signer (self-unjail when equal to reporter_address). + string signer_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // reporter_address is the jailed reporter or selector to unjail. + string reporter_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } // MsgUnjailReporterResponse defines the Msg/UnjailReporter response type. diff --git a/tests/integration/dispute_keeper_test.go b/tests/integration/dispute_keeper_test.go index 666bf21d2..9512df5a5 100644 --- a/tests/integration/dispute_keeper_test.go +++ b/tests/integration/dispute_keeper_test.go @@ -235,6 +235,7 @@ func (s *IntegrationTestSuite) TestProposeDisputeFromBond() { reporterServer := reporterKeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) req := &reportertypes.MsgUnjailReporter{ + SignerAddress: repAddr.String(), ReporterAddress: repAddr.String(), } _, err = reporterServer.UnjailReporter(s.Setup.Ctx, req) @@ -367,6 +368,7 @@ func (s *IntegrationTestSuite) TestExecuteVoteInvalid() { reporterServer := reporterKeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) req := &reportertypes.MsgUnjailReporter{ + SignerAddress: repAddr.String(), ReporterAddress: repAddr.String(), } _, err = reporterServer.UnjailReporter(s.Setup.Ctx, req) diff --git a/tests/integration/dispute_test.go b/tests/integration/dispute_test.go index 9d0c56352..3706d188c 100644 --- a/tests/integration/dispute_test.go +++ b/tests/integration/dispute_test.go @@ -311,6 +311,7 @@ func (s *IntegrationTestSuite) TestDisputes() { // create msgUnJailReporter msgUnjailReporter := reportertypes.MsgUnjailReporter{ + SignerAddress: reporterAccount.String(), ReporterAddress: reporterAccount.String(), } // send unjailreporter tx @@ -495,6 +496,7 @@ func (s *IntegrationTestSuite) TestDisputes() { s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(disputekeeper.THREE_DAYS)) // call unjail function msgUnjailReporter = reportertypes.MsgUnjailReporter{ + SignerAddress: reporterAccount.String(), ReporterAddress: reporterAccount.String(), } _, err = msgServerReporter.UnjailReporter(s.Setup.Ctx, &msgUnjailReporter) @@ -1092,8 +1094,8 @@ func (s *IntegrationTestSuite) TestOpenDisputePrecision() { require.Equal(expectedAnnaPower.String(), annaReporterStake.String()) // chris tips random fraction of trb to get matic/usd spot price - // tip is between 1 loya and 1 trb - randomTipAmount := math.NewInt(rand.Int63n(1*1e6) + 1) + // tip is between 1000 loya and 1 trb + randomTipAmount := math.NewInt(rand.Int63n((1*1e6)-1000+1) + 1000) maticQueryData := s.Setup.CreateSpotPriceTip(ctx, chrisAccAddr, `["matic","usd"]`, randomTipAmount) _, err = s.Setup.App.EndBlocker(ctx) @@ -1458,6 +1460,7 @@ func (s *IntegrationTestSuite) TestDisputes2() { // disputed reporter can report after calling unjail function msgUnjail := reportertypes.MsgUnjailReporter{ + SignerAddress: repsAccs[0].String(), ReporterAddress: repsAccs[0].String(), } _, err = msgServerReporter.UnjailReporter(s.Setup.Ctx, &msgUnjail) @@ -1576,6 +1579,7 @@ func (s *IntegrationTestSuite) TestDisputes2() { // disputed reporter can report after calling unjail function msgUnjail = reportertypes.MsgUnjailReporter{ + SignerAddress: repsAccs[0].String(), ReporterAddress: repsAccs[0].String(), } _, err = msgServerReporter.UnjailReporter(s.Setup.Ctx, &msgUnjail) diff --git a/tests/integration/reporter_keeper_test.go b/tests/integration/reporter_keeper_test.go index ec97e5a82..55976b78c 100644 --- a/tests/integration/reporter_keeper_test.go +++ b/tests/integration/reporter_keeper_test.go @@ -1,6 +1,7 @@ package integration_test import ( + "bytes" "fmt" "time" @@ -11,6 +12,7 @@ import ( "github.com/tellor-io/layer/utils" oraclekeeper "github.com/tellor-io/layer/x/oracle/keeper" oracletypes "github.com/tellor-io/layer/x/oracle/types" + registrytypes "github.com/tellor-io/layer/x/registry/types" "github.com/tellor-io/layer/x/reporter/keeper" reportertypes "github.com/tellor-io/layer/x/reporter/types" @@ -122,15 +124,126 @@ func (s *IntegrationTestSuite) TestSwitchReporterMsg() { // valrep1 should have more tokens than valrep2 s.True(validatorReporter1.GT(validatorReporter2)) - // change reporter - s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) - s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(time.Now()) + // Bridge deposit (TRBBridgeV2) uses a long open commitment (expiration height + // recorded as max open commitment on the outgoing reporter), deferring switch + // finalization until that height is passed. + spec := registrytypes.DataSpec{ + AbiComponents: []*registrytypes.ABIComponent{ + {Name: "tolayer", FieldType: "bool"}, + {Name: "depositId", FieldType: "uint256"}, + }, + } + bridgeQueryData, err := spec.EncodeData("TRBBridgeV2", `["true","4242"]`) + s.NoError(err) + queryID := utils.QueryIDFromData(bridgeQueryData) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + bridgeHeight := int64(10) + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight).WithBlockTime(time.Now()) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: valAccs[0].String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, valAccs[0].Bytes()) + s.NoError(err) + s.Equal(uint64(bridgeHeight)+2000, maxCommit) + + qMeta, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + rep1PowerExpect := validatorReporter1.Quo(layertypes.PowerReduction).Uint64() + rep1Report, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, valAccs[0].Bytes(), qMeta.Id)) + s.NoError(err) + s.Equal(rep1PowerExpect, rep1Report.Power, "first reporter should report with selector stake included") + + // Next block, then deferred switch. + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{SelectorAddress: newDelegator.String(), ReporterAddress: valAccs[1].String()}) s.NoError(err) - // forward time to bypass the lock time that the delegator has + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, valAccs[0].Bytes()), "assignment must not flip until pending switch is applied") + + outPK := collections.Join(valAccs[0].Bytes(), newDelegator.Bytes()) + pendingOut, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, outPK) + s.NoError(err) + s.True(bytes.Equal(pendingOut.ToReporter, valAccs[1].Bytes())) + s.Equal(sel.SwitchOutLockedUntilBlock, pendingOut.UnlockBlock) + s.Equal(maxCommit, pendingOut.UnlockBlock) + + inPK := collections.Join(valAccs[1].Bytes(), newDelegator.Bytes()) + fromB, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Get(s.Setup.Ctx, inPK) + s.NoError(err) + s.True(bytes.Equal(fromB, valAccs[0].Bytes())) + + // A few blocks later the bridge window is still open, but the selector handoff + // is not done — the incoming reporter must not count the selector's stake. + postSwitchH := bridgeHeight + 1 + 2 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(postSwitchH).WithBlockTime(s.Setup.Ctx.BlockTime().Add(2 * time.Second)) + s.True(uint64(postSwitchH) < maxCommit, "reporting window for the first report must still be open") + + rep2Stake, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[1], queryID) + s.NoError(err) + s.True(rep2Stake.Equal(validatorReporter2), "selector power must not count toward incoming reporter before finalization") + expectedRep2Power := rep2Stake.Quo(layertypes.PowerReduction).Uint64() + + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: valAccs[1].String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + + rep2Report, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, valAccs[1].Bytes(), qMeta.Id)) + s.NoError(err) + s.Equal(expectedRep2Power, rep2Report.Power) + s.True(rep2Report.Power < rep1Report.Power, "second reporter must not include the selector's stake while switch is pending") + + // Past the open commitment height so ReporterStake on the outgoing reporter can finalize. + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Hour)) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], queryID) + s.NoError(err) + + hasPending, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, outPK) + s.NoError(err) + s.False(hasPending, "pending row must be removed after finalization") + + sel, err = s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, valAccs[1].Bytes())) + + // Different bridge deposit after handoff: reporter 1's oracle power must not include the + // selector, who now belongs to reporter 2. + bridgeQueryDataPost, err := spec.EncodeData("TRBBridgeV2", `["true","4243"]`) + s.NoError(err) + queryIDPost := utils.QueryIDFromData(bridgeQueryDataPost) + + postFinalizeH := finalizeHeight + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(postFinalizeH).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Minute)) + + rep1StakePostSwitch, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], queryIDPost) + s.NoError(err) + s.True(rep1StakePostSwitch.LT(val1.Tokens), "reporter1 must no longer count the selector's bonded stake") + expectedRep1PowerPost := rep1StakePostSwitch.Quo(layertypes.PowerReduction).Uint64() + + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: valAccs[0].String(), + QueryData: bridgeQueryDataPost, + Value: bridgeTestValue, + }) + s.NoError(err) + + qMetaPost, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryIDPost) s.NoError(err) - s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(1814400 * time.Second).Add(1)) + rep1PostReport, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryIDPost, valAccs[0].Bytes(), qMetaPost.Id)) + s.NoError(err) + s.Equal(expectedRep1PowerPost, rep1PostReport.Power, "post-switch report must use stake without former selector") + s.True(rep1PostReport.Power < rep1Report.Power, "reporter1 power on new query should be below pre-switch report that included selector") + // check validator reporting tokens after delegator has moved validatorReporter1, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], []byte{}) s.NoError(err) @@ -143,6 +256,90 @@ func (s *IntegrationTestSuite) TestSwitchReporterMsg() { s.True(validatorReporter2.GT(validatorReporter1)) } +// TestSwitchReporterReplacesPendingTargetIntegration checks that a second +// SwitchReporter while a row is still pending replaces ToReporter and incoming +// index without bumping UnlockBlock (replace path in scheduleReporterSwitch). +func (s *IntegrationTestSuite) TestSwitchReporterReplacesPendingTargetIntegration() { + msgServer := keeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + stakingMsgServer := stakingkeeper.NewMsgServerImpl(s.Setup.Stakingkeeper) + valAccs, valAddrs, _ := s.createValidatorAccs([]uint64{100, 200, 300}) + + newDelegator := sample.AccAddressBytes() + s.Setup.MintTokens(newDelegator, math.NewInt(1000*1e6)) + msgDelegate := stakingtypes.NewMsgDelegate( + newDelegator.String(), + valAddrs[0].String(), + sdk.NewInt64Coin(s.Setup.Denom, 1000*1e6), + ) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(1) + _, err := stakingMsgServer.Delegate(s.Setup.Ctx, msgDelegate) + s.NoError(err) + + for i := 0; i < 3; i++ { + _, err = msgServer.CreateReporter(s.Setup.Ctx, &reportertypes.MsgCreateReporter{ + ReporterAddress: valAccs[i].String(), + CommissionRate: reportertypes.DefaultMinCommissionRate, + MinTokensRequired: math.NewIntWithDecimal(1, 6), + Moniker: fmt.Sprintf("rep%d", i), + }) + s.NoError(err) + } + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(2) + _, err = msgServer.SelectReporter(s.Setup.Ctx, &reportertypes.MsgSelectReporter{ + SelectorAddress: newDelegator.String(), + ReporterAddress: valAccs[0].String(), + }) + s.NoError(err) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(time.Now()) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: newDelegator.String(), + ReporterAddress: valAccs[1].String(), + }) + s.NoError(err) + + outPK := collections.Join(valAccs[0].Bytes(), newDelegator.Bytes()) + first, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, outPK) + s.NoError(err) + s.True(bytes.Equal(first.ToReporter, valAccs[1].Bytes())) + unlockAfterFirst := first.UnlockBlock + + // Replace target before outgoing reporter runs ReporterStake: same unlock, new ToReporter. + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: newDelegator.String(), + ReporterAddress: valAccs[2].String(), + }) + s.NoError(err) + + second, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, outPK) + s.NoError(err) + s.Equal(unlockAfterFirst, second.UnlockBlock, "replace must not re-query oracle / bump unlock") + s.True(bytes.Equal(second.ToReporter, valAccs[2].Bytes())) + + hasIn1, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Has(s.Setup.Ctx, collections.Join(valAccs[1].Bytes(), newDelegator.Bytes())) + s.NoError(err) + s.False(hasIn1, "stale incoming index for replaced target must be removed") + + fromB, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Get(s.Setup.Ctx, collections.Join(valAccs[2].Bytes(), newDelegator.Bytes())) + s.NoError(err) + s.True(bytes.Equal(fromB, valAccs[0].Bytes())) + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, valAccs[0].Bytes())) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], []byte{}) + s.NoError(err) + + sel, err = s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, valAccs[2].Bytes())) +} + func (s *IntegrationTestSuite) TestAddAmountToStake() { s.Setup.CreateValidators(5) @@ -540,9 +737,6 @@ func (s *IntegrationTestSuite) TestCreateAndSwitchReporterMsg() { msStaking := stakingkeeper.NewMsgServerImpl(s.Setup.Stakingkeeper) require.NotNil(msStaking) - msOracle := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) - require.NotNil(msOracle) - valAccs, valAddrs, _ := s.createValidatorAccs([]uint64{100, 200}) newDelegator := sample.AccAddressBytes() s.Setup.MintTokens(newDelegator, math.NewInt(1000*1e6)) @@ -597,29 +791,87 @@ func (s *IntegrationTestSuite) TestCreateAndSwitchReporterMsg() { }) s.NoError(err) - // check delegator reporter in selectors collections + // Pending self-promotion: outgoing reporter stays active until ReporterStake applies it. formerSelector, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator) s.NoError(err) + s.Equal(formerSelector.Reporter, valAccs[0].Bytes()) + pk := collections.Join(valAccs[0].Bytes(), newDelegator.Bytes()) + has, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, pk) + s.NoError(err) + s.True(has) + + promoteEnt, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, pk) + s.NoError(err) + s.True(bytes.Equal(promoteEnt.ToReporter, newDelegator.Bytes())) + s.Equal(formerSelector.SwitchOutLockedUntilBlock, promoteEnt.UnlockBlock) + + inPromotePK := collections.Join(newDelegator.Bytes(), newDelegator.Bytes()) + fromReporter, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Get(s.Setup.Ctx, inPromotePK) + s.NoError(err) + s.True(bytes.Equal(fromReporter, valAccs[0].Bytes())) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], []byte{}) + s.NoError(err) + + has, err = s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, pk) + s.NoError(err) + s.False(has) + hasIn, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Has(s.Setup.Ctx, inPromotePK) + s.NoError(err) + s.False(hasIn) + + formerSelector, err = s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator) + s.NoError(err) s.Equal(formerSelector.Reporter, newDelegator.Bytes()) // check delegator reporter exists in reporters collections reporterExists, err := s.Setup.Reporterkeeper.Reporters.Has(s.Setup.Ctx, newDelegator) s.NoError(err) s.True(reporterExists) - // delegator reporter decides to go back to delegator selector + // A second switch request within 21 days of the last request is rejected unless + // a pending row already exists; advance wall-clock so the next attempt clears the gate. + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(22 * 24 * time.Hour)) + + // delegator reporter decides to go back to delegator selector (pending demotion + // until ReporterStake on the outgoing self-reporter). _, err = msReporter.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{SelectorAddress: newDelegator.String(), ReporterAddress: valAccs[0].String()}) s.NoError(err) - // check delegator reporter in selectors collections - formerSelector, err = s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator) + demotePK := collections.Join(newDelegator.Bytes(), newDelegator.Bytes()) + hasDem, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, demotePK) s.NoError(err) - s.Equal(formerSelector.Reporter, valAccs[0].Bytes()) + s.True(hasDem) + demEnt, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, demotePK) + s.NoError(err) + s.True(bytes.Equal(demEnt.ToReporter, valAccs[0].Bytes())) + + selDem, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selDem.Reporter, newDelegator.Bytes()), "still self-reporter until pending demotion finalizes") + s.Equal(selDem.SwitchOutLockedUntilBlock, demEnt.UnlockBlock) + + inDemPK := collections.Join(valAccs[0].Bytes(), newDelegator.Bytes()) + fromSelf, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Get(s.Setup.Ctx, inDemPK) + s.NoError(err) + s.True(bytes.Equal(fromSelf, newDelegator.Bytes())) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], []byte{}) + s.NoError(err) + + hasDem, err = s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, demotePK) + s.NoError(err) + s.False(hasDem) // check delegator reporter does not exist in reporters collections reporterExists, err = s.Setup.Reporterkeeper.Reporters.Has(s.Setup.Ctx, newDelegator) s.NoError(err) s.False(reporterExists) - // delegator becomes reporter again + // Advance wall-clock again past the 21-day switch-request gate before the next self-promotion. + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(22 * 24 * time.Hour)) + + // delegator becomes reporter again (second pending self-promotion from val0) _, err = msReporter.CreateReporter(s.Setup.Ctx, &reportertypes.MsgCreateReporter{ ReporterAddress: newDelegator.String(), CommissionRate: reportertypes.DefaultMinCommissionRate, @@ -628,6 +880,22 @@ func (s *IntegrationTestSuite) TestCreateAndSwitchReporterMsg() { }) s.NoError(err) + pk2 := collections.Join(valAccs[0].Bytes(), newDelegator.Bytes()) + has2, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, pk2) + s.NoError(err) + s.True(has2) + secEnt, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, pk2) + s.NoError(err) + s.True(bytes.Equal(secEnt.ToReporter, newDelegator.Bytes())) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, valAccs[0], []byte{}) + s.NoError(err) + + has2, err = s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, pk2) + s.NoError(err) + s.False(has2) + // check delegator reporter in selectors collections formerSelector, err = s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, newDelegator) s.NoError(err) diff --git a/tests/integration/reporter_switch_test.go b/tests/integration/reporter_switch_test.go new file mode 100644 index 000000000..0f891c62f --- /dev/null +++ b/tests/integration/reporter_switch_test.go @@ -0,0 +1,1306 @@ +package integration_test + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "time" + + "github.com/tellor-io/layer/testutil/sample" + layertypes "github.com/tellor-io/layer/types" + "github.com/tellor-io/layer/utils" + "github.com/tellor-io/layer/x/dispute" + disputekeeper "github.com/tellor-io/layer/x/dispute/keeper" + disputetypes "github.com/tellor-io/layer/x/dispute/types" + oraclekeeper "github.com/tellor-io/layer/x/oracle/keeper" + oracletypes "github.com/tellor-io/layer/x/oracle/types" + registrytypes "github.com/tellor-io/layer/x/registry/types" + reporterkeeper "github.com/tellor-io/layer/x/reporter/keeper" + reportertypes "github.com/tellor-io/layer/x/reporter/types" + + "cosmossdk.io/collections" + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// reporterSwitchFixture wires three validator-reporters (A,B,C) and one external selector. +func (s *IntegrationTestSuite) reporterSwitchFixture() ( + reporterA, reporterB, reporterC, selector sdk.AccAddress, + bridgeQueryData, queryID []byte, +) { + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + stakingMsgServer := stakingkeeper.NewMsgServerImpl(s.Setup.Stakingkeeper) + + valAccs, valAddrs, _ := s.createValidatorAccs([]uint64{100, 200, 300}) + reporterA, reporterB, reporterC = valAccs[0], valAccs[1], valAccs[2] + + selector = sample.AccAddressBytes() + s.Setup.MintTokens(selector, math.NewInt(1000*1e6)) + msgDelegate := stakingtypes.NewMsgDelegate( + selector.String(), + valAddrs[0].String(), + sdk.NewInt64Coin(s.Setup.Denom, 1000*1e6), + ) + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(1) + _, err := stakingMsgServer.Delegate(s.Setup.Ctx, msgDelegate) + s.NoError(err) + + for i, rep := range []sdk.AccAddress{reporterA, reporterB, reporterC} { + _, err = msgServer.CreateReporter(s.Setup.Ctx, &reportertypes.MsgCreateReporter{ + ReporterAddress: rep.String(), + CommissionRate: reportertypes.DefaultMinCommissionRate, + MinTokensRequired: math.NewIntWithDecimal(1, 6), + Moniker: fmt.Sprintf("switch_rep_%d", i), + }) + s.NoError(err) + } + + // MsgSelectReporter sets the Selection and FlagStakeRecalc(reporterA); prefer it over + // assignSelectorToReporter unless you need to bypass msg validation. + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(2) + _, err = msgServer.SelectReporter(s.Setup.Ctx, &reportertypes.MsgSelectReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterA.String(), + }) + s.NoError(err) + + spec := registrytypes.DataSpec{ + AbiComponents: []*registrytypes.ABIComponent{ + {Name: "tolayer", FieldType: "bool"}, + {Name: "depositId", FieldType: "uint256"}, + }, + } + bridgeQueryData, err = spec.EncodeData("TRBBridgeV2", `["true","9001"]`) + s.NoError(err) + queryID = utils.QueryIDFromData(bridgeQueryData) + return reporterA, reporterB, reporterC, selector, bridgeQueryData, queryID +} + +func (s *IntegrationTestSuite) submitBridgeReport( + reporter sdk.AccAddress, + bridgeQueryData []byte, + height int64, +) (oracletypes.MicroReport, uint64) { + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(height).WithBlockTime(time.Now()) + _, err := oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporter.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + + queryID := utils.QueryIDFromData(bridgeQueryData) + qMeta, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + report, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporter.Bytes(), qMeta.Id)) + s.NoError(err) + return report, uint64(height) +} + +// proposeFullMinorDispute pays the full minor fee in one shot (jail + voting) and returns the new dispute id. +func (s *IntegrationTestSuite) proposeFullMinorDispute(disputer sdk.AccAddress, report oracletypes.MicroReport) uint64 { + s.Setup.MintTokens(disputer, math.NewInt(20_000_000_000)) + disputeFee, err := s.Setup.Disputekeeper.GetDisputeFee(s.Setup.Ctx, report, disputetypes.Minor) + s.NoError(err) + + disputeID := s.Setup.Disputekeeper.NextDisputeId(s.Setup.Ctx) + disputeMsgServer := disputekeeper.NewMsgServerImpl(s.Setup.Disputekeeper) + _, err = disputeMsgServer.ProposeDispute(s.Setup.Ctx, &disputetypes.MsgProposeDispute{ + Creator: disputer.String(), + DisputedReporter: report.Reporter, + ReportMetaId: report.MetaId, + ReportQueryId: hex.EncodeToString(report.QueryId), + Fee: sdk.NewCoin(s.Setup.Denom, disputeFee), + DisputeCategory: disputetypes.Minor, + }) + s.NoError(err) + return disputeID +} + +func disputeTallyTotals(t disputetypes.StakeholderVoteCounts) (reporters, users, team uint64) { + reporters = t.Reporters.Support + t.Reporters.Against + t.Reporters.Invalid + users = t.Users.Support + t.Users.Against + t.Users.Invalid + team = t.Team.Support + t.Team.Against + t.Team.Invalid + return +} + +// disputeTallyOrZero treats a missing VoteCountsByGroup entry as an empty tally (pre-first-vote state). +func (s *IntegrationTestSuite) disputeTallyOrZero(disputeID uint64) disputetypes.StakeholderVoteCounts { + tally, err := s.Setup.Disputekeeper.VoteCountsByGroup.Get(s.Setup.Ctx, disputeID) + if errors.Is(err, collections.ErrNotFound) { + return disputetypes.StakeholderVoteCounts{} + } + s.NoError(err) + return tally +} + +// snapshotAtReportBlockHasSelector matches dispute jail: nearest delegation snapshot at or before reportBlock. +func (s *IntegrationTestSuite) snapshotAtReportBlockHasSelector( + reporter sdk.AccAddress, reportBlock uint64, selector sdk.AccAddress, +) bool { + snap, err := s.Setup.Reporterkeeper.GetDelegationsAmount(s.Setup.Ctx, reporter.Bytes(), reportBlock) + if err != nil { + return false + } + for _, o := range snap.TokenOrigins { + if bytes.Equal(o.DelegatorAddress, selector.Bytes()) { + return true + } + } + return false +} + +// TestReporterSwitchStakeExclusionAndDisputeSnapshot verifies stake handoff rules and that +// a minor dispute on reporter A's pre-switch report jails the selector from the block snapshot. +func (s *IntegrationTestSuite) TestReporterSwitchStakeExclusionAndDisputeSnapshot() { + reporterA, reporterB, reporterC, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + valA, err := s.Setup.Stakingkeeper.GetValidator(s.Setup.Ctx, sdk.ValAddress(reporterA)) + s.NoError(err) + + bridgeHeight := int64(10) + report, reportBlock := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + + stakeAWithSelector, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterA, queryID) + s.NoError(err) + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(stakeAWithSelector.GT(stakeBBase)) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.Equal(uint64(bridgeHeight)+2000, maxCommit) + + qMeta, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + expectedAPower := stakeAWithSelector.Quo(layertypes.PowerReduction).Uint64() + s.Equal(expectedAPower, report.Power, "reporter A's report must include selector stake") + + s.True(s.snapshotAtReportBlockHasSelector(reporterA, reportBlock, selector), + "delegation snapshot at report block must include selector before switch") + + // Dispute before switch so jail uses the report-block snapshot that still lists the selector. + disputer := s.newKeysWithTokens() + s.proposeFullMinorDispute(disputer, report) + + repA, err := s.Setup.Reporterkeeper.Reporters.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.True(repA.Jailed) + + selAfterDispute, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(reportertypes.SelectorStakeLocked(selAfterDispute, s.Setup.Ctx.BlockTime()), + "selector from report snapshot must be dispute-locked") + + // Deferred switch A → B while the bridge window is still open. + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, reporterA.Bytes()), "selection must stay on A until finalize") + + outPK := collections.Join(reporterA.Bytes(), selector.Bytes()) + hasPending, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, outPK) + s.NoError(err) + s.True(hasPending, "pending switch must be recorded on outgoing reporter A") + + // Reporter A is jailed after the dispute; verify exclusion via B and the pending row. + stakeBPending, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBPending, "B must not gain selector stake before finalize") + + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + repBReport, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMeta.Id)) + s.NoError(err) + s.Equal(stakeBPending.Quo(layertypes.PowerReduction).Uint64(), repBReport.Power) + s.True(repBReport.Power < report.Power, "B reporting same query must not include switched selector") + + // Finalize switch after open commitment expires. Stay within minor jail (600s) so the selector + // remains dispute-locked and must not count toward B. + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(30 * time.Second)) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + + selFinal, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selFinal.Reporter, reporterB.Bytes()), "switch must finalize onto B after unlock height") + s.True(reportertypes.SelectorStakeLocked(selFinal, s.Setup.Ctx.BlockTime()), + "selector must still be dispute-locked within minor jail window") + + stakeBAfterFinalize, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBAfterFinalize, "dispute-locked selector must not count toward B after finalize") + + // Reporter A is jailed; do not call ReporterStake on A (it errors). Stake was already excluded while pending. + _ = reporterC + _ = valA +} + +// TestReporterSwitchDisputeAfterSwitch verifies that a minor dispute on reporter A's report still +// jails and locks selectors from the report-block snapshot when the switch happened first. +func (s *IntegrationTestSuite) TestReporterSwitchDisputeAfterSwitch() { + s.Run("pending_switch_then_dispute", func() { + s.SetupTest() + reporterA, reporterB, _, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + bridgeHeight := int64(10) + report, reportBlock := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(s.snapshotAtReportBlockHasSelector(reporterA, reportBlock, selector)) + + // Switch before dispute; selection stays on A until finalize. + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, reporterA.Bytes()), "selection must stay on A until finalize") + s.False(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime()), + "selector must not be dispute-locked before dispute") + + disputer := s.newKeysWithTokens() + s.proposeFullMinorDispute(disputer, report) + + repA, err := s.Setup.Reporterkeeper.Reporters.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.True(repA.Jailed) + + selAfter, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(reportertypes.SelectorStakeLocked(selAfter, s.Setup.Ctx.BlockTime()), + "selector must be dispute-locked from report snapshot despite pending switch to B") + s.True(bytes.Equal(selAfter.Reporter, reporterA.Bytes())) + + stakeBPending, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBPending, "B must not gain selector stake while locked and pending") + + qMeta, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + repBReport, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMeta.Id)) + s.NoError(err) + s.Equal(stakeBPending.Quo(layertypes.PowerReduction).Uint64(), repBReport.Power) + s.True(repBReport.Power < report.Power) + }) + + s.Run("finalized_switch_then_dispute", func() { + s.SetupTest() + reporterA, reporterB, _, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + + bridgeHeight := int64(10) + report, reportBlock := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(s.snapshotAtReportBlockHasSelector(reporterA, reportBlock, selector)) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(2 * time.Hour)) + stakeBWithSelector, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(stakeBWithSelector.GT(stakeBBase), "B must include selector stake after finalize before dispute") + + selFinal, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selFinal.Reporter, reporterB.Bytes()), "switch must finalize onto B before dispute") + + disputer := s.newKeysWithTokens() + s.proposeFullMinorDispute(disputer, report) + + repA, err := s.Setup.Reporterkeeper.Reporters.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.True(repA.Jailed) + + selAfter, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selAfter.Reporter, reporterB.Bytes()), + "selection must remain on B after dispute; lock is on selector row not live index") + s.True(reportertypes.SelectorStakeLocked(selAfter, s.Setup.Ctx.BlockTime()), + "selector must be dispute-locked from A's report snapshot even though no longer on A") + + stakeBAfterDispute, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBAfterDispute, + "dispute-locked selector must not count toward B after switch finalized") + }) +} + +// TestReporterSwitchDisputeOnFormerReporter verifies that after a selector switches from +// reporter A to B (pending over a long bridge open-commitment window) and A's pre-switch +// report is disputed, the selector stays dispute-locked through switch finalization, B's +// oracle power excludes them until jail expires, then includes them again. +func (s *IntegrationTestSuite) TestReporterSwitchDisputeOnFormerReporter() { + const minorJailDuration = 600 * time.Second + + reporterA, reporterB, _, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + bridgeHeight := int64(10) + report, reportBlock := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + s.True(s.snapshotAtReportBlockHasSelector(reporterA, reportBlock, selector)) + + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.Equal(uint64(bridgeHeight)+2000, maxCommit, "bridge report must defer switch over a long block window") + + qMeta, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + + // Switch A → B while the bridge commitment is still open (pending until maxCommit). + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + selPending, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selPending.Reporter, reporterA.Bytes()), "selection must stay on A until finalize") + + disputer := s.newKeysWithTokens() + disputeTime := s.Setup.Ctx.BlockTime() + s.proposeFullMinorDispute(disputer, report) + + selLocked, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selLocked.Reporter, reporterA.Bytes()), "still on A while switch pending") + s.True(selLocked.DisputeLockedUntil.After(disputeTime)) + s.True(reportertypes.SelectorStakeLocked(selLocked, s.Setup.Ctx.BlockTime()), + "selector must be dispute-locked from A's report snapshot") + + stakeBPendingLocked, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBPendingLocked, "B must not gain selector stake while pending and dispute-locked") + + // B reports while switch is still pending and selector is dispute-locked. + pendingReportHeight := bridgeHeight + 2 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(pendingReportHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(10 * time.Second)) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + repBPending, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMeta.Id)) + s.NoError(err) + s.Equal(stakeBPendingLocked.Quo(layertypes.PowerReduction).Uint64(), repBPending.Power, + "B report power must exclude selector while switch pending and dispute-locked") + s.True(repBPending.Power < report.Power) + + // Finalize after the long open-commitment height, but stay within minor jail (600s wall clock) + // so the selector is still dispute-locked when the handoff completes. + finalizeHeight := int64(maxCommit) + 1 + s.True(finalizeHeight > pendingReportHeight, "finalize must wait for the long reporting window") + elapsedSinceDispute := 30 * time.Second + s.True(elapsedSinceDispute < minorJailDuration) + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(disputeTime.Add(elapsedSinceDispute)) + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + + selFinal, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selFinal.Reporter, reporterB.Bytes()), "switch must finalize onto B after unlock height") + s.True(reportertypes.SelectorStakeLocked(selFinal, s.Setup.Ctx.BlockTime()), + "selector must still be dispute-locked when switch finalizes after long reporting window") + s.True(s.Setup.Ctx.BlockTime().Before(selFinal.DisputeLockedUntil), + "dispute lock must outlive switch finalization in this scenario") + + stakeBAfterFinalizeLocked, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBAfterFinalizeLocked, "dispute-locked selector must not count toward B after finalize") + + lockedReportHeight := finalizeHeight + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(lockedReportHeight).WithBlockTime(disputeTime.Add(elapsedSinceDispute + time.Second)) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + qMetaAfterFinalize, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + repBWhileLocked, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMetaAfterFinalize.Id)) + s.NoError(err) + expectedLockedPower := stakeBAfterFinalizeLocked.Quo(layertypes.PowerReduction).Uint64() + s.Equal(expectedLockedPower, repBWhileLocked.Power, + "B report power after finalize must still exclude selector while dispute-locked") + s.True(repBWhileLocked.Power < report.Power) + + // Advance past minor jail wall-clock duration (from dispute time, not finalize time). + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(disputeTime.Add(minorJailDuration + time.Second)) + + selUnlocked, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.False(reportertypes.SelectorStakeLocked(selUnlocked, s.Setup.Ctx.BlockTime()), + "dispute lock must expire after jail duration") + + stakeBAfterUnlock, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(stakeBAfterUnlock.GT(stakeBBase), "B must regain full selector stake after lock expiry") + + spec := registrytypes.DataSpec{ + AbiComponents: []*registrytypes.ABIComponent{ + {Name: "tolayer", FieldType: "bool"}, + {Name: "depositId", FieldType: "uint256"}, + }, + } + bridgeQueryDataUnlocked, err := spec.EncodeData("TRBBridgeV2", `["true","9002"]`) + s.NoError(err) + queryIDUnlocked := utils.QueryIDFromData(bridgeQueryDataUnlocked) + + unlockedReportHeight := lockedReportHeight + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(unlockedReportHeight) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryDataUnlocked, + Value: bridgeTestValue, + }) + s.NoError(err) + + qMetaUnlocked, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryIDUnlocked) + s.NoError(err) + repBAfterUnlock, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryIDUnlocked, reporterB.Bytes(), qMetaUnlocked.Id)) + s.NoError(err) + expectedUnlockedPower := stakeBAfterUnlock.Quo(layertypes.PowerReduction).Uint64() + s.Equal(expectedUnlockedPower, repBAfterUnlock.Power, + "B report power after dispute lock expiry must include selector stake") + s.True(repBAfterUnlock.Power > repBWhileLocked.Power, + "B must gain oracle power from selector after dispute lock expires") +} + +// TestReporterSwitchPendingEdgeCases covers replace/idempotent pending switches and lock gating. +func (s *IntegrationTestSuite) TestReporterSwitchPendingEdgeCases() { + s.Run("idempotent_switch_to_same_pending_target", func() { + s.SetupTest() + reporterA, reporterB, _, selector, _, _ := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(5) + _, err := msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err, "second switch to same pending target must be a no-op success") + + outPK := collections.Join(reporterA.Bytes(), selector.Bytes()) + ent, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, outPK) + s.NoError(err) + s.True(bytes.Equal(ent.ToReporter, reporterB.Bytes())) + }) + + s.Run("replace_pending_target_while_lock_active", func() { + s.SetupTest() + reporterA, reporterB, reporterC, selector, bridgeQueryData, _ := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + + _, _ = s.submitBridgeReport(reporterA, bridgeQueryData, 10) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(11) + _, err := msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.Greater(sel.SwitchOutLockedUntilBlock, uint64(s.Setup.Ctx.BlockHeight())) + + // Replace B with C while the outgoing lock is still active (hasPending bypasses lock rejection). + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterC.String(), + }) + s.NoError(err) + + outPK := collections.Join(reporterA.Bytes(), selector.Bytes()) + ent, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, outPK) + s.NoError(err) + s.True(bytes.Equal(ent.ToReporter, reporterC.Bytes())) + + hasB, err := s.Setup.Reporterkeeper.IncomingPendingSwitchIdx.Has(s.Setup.Ctx, collections.Join(reporterB.Bytes(), selector.Bytes())) + s.NoError(err) + s.False(hasB) + }) + + s.Run("rejects_new_switch_when_outgoing_lock_active_without_pending_row", func() { + s.SetupTest() + reporterA, reporterB, reporterC, selector, _, _ := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(20) + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + sel.SwitchOutLockedUntilBlock = 100 + s.NoError(s.Setup.Reporterkeeper.Selectors.Set(s.Setup.Ctx, selector.Bytes(), sel)) + + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.ErrorContains(err, "selector is locked until the current reporter switch completes") + + // Different target also blocked. + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterC.String(), + }) + s.ErrorContains(err, "selector is locked until the current reporter switch completes") + _ = reporterA + }) +} + +// TestReporterSwitchSkipIntermediateReporter verifies A→B→C replace finalizes on C with selector stake +// once unlock_block < current height, without B ever reporting. +func (s *IntegrationTestSuite) TestReporterSwitchSkipIntermediateReporter() { + reporterA, reporterB, reporterC, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + + valC, err := s.Setup.Stakingkeeper.GetValidator(s.Setup.Ctx, sdk.ValAddress(reporterC)) + s.NoError(err) + stakeCBefore, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterC, queryID) + s.NoError(err) + s.Equal(valC.Tokens, stakeCBefore) + + bridgeHeight := int64(10) + _, _ = s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterC.String(), + }) + s.NoError(err) + + outPK := collections.Join(reporterA.Bytes(), selector.Bytes()) + ent, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Get(s.Setup.Ctx, outPK) + s.NoError(err) + s.True(bytes.Equal(ent.ToReporter, reporterC.Bytes())) + + // Past open commitment: finalize via ReporterStake on incoming reporter C only. + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(2 * time.Hour)) + stakeCAfter, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterC, queryID) + s.NoError(err) + s.True(stakeCAfter.GT(stakeCBefore), "C must include selector stake immediately after unlock when B never reported") + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(sel.Reporter, reporterC.Bytes())) + + hasPending, err := s.Setup.Reporterkeeper.OutgoingPendingSwitches.Has(s.Setup.Ctx, outPK) + s.NoError(err) + s.False(hasPending) +} + +// disputeOutcomeCase drives tally/execute for a jailed minor dispute after a reporter switch. +type disputeOutcomeCase struct { + name string + votes []disputetypes.MsgVote + expectResult disputetypes.VoteResult + reporterJailed bool + selectorLocked bool +} + +// TestReporterSwitchMinorDisputeOutcomes exercises vote results on a report that was made before +// the selector switched away; dispute is opened before the switch so the snapshot includes the selector. +func (s *IntegrationTestSuite) TestReporterSwitchMinorDisputeOutcomes() { + cases := []disputeOutcomeCase{ + { + name: "support", + votes: []disputetypes.MsgVote{ + {Vote: disputetypes.VoteEnum_VOTE_SUPPORT}, + {Vote: disputetypes.VoteEnum_VOTE_SUPPORT}, + {Vote: disputetypes.VoteEnum_VOTE_SUPPORT}, + }, + expectResult: disputetypes.VoteResult_SUPPORT, + reporterJailed: true, + selectorLocked: true, + }, + { + name: "against", + votes: []disputetypes.MsgVote{ + {Vote: disputetypes.VoteEnum_VOTE_AGAINST}, + {Vote: disputetypes.VoteEnum_VOTE_AGAINST}, + {Vote: disputetypes.VoteEnum_VOTE_AGAINST}, + }, + expectResult: disputetypes.VoteResult_AGAINST, + reporterJailed: false, + selectorLocked: false, + }, + { + name: "invalid", + votes: []disputetypes.MsgVote{ + {Vote: disputetypes.VoteEnum_VOTE_INVALID}, + {Vote: disputetypes.VoteEnum_VOTE_INVALID}, + {Vote: disputetypes.VoteEnum_VOTE_INVALID}, + }, + expectResult: disputetypes.VoteResult_INVALID, + reporterJailed: false, + selectorLocked: false, + }, + { + name: "no_quorum_majority_support", + votes: []disputetypes.MsgVote{ + {Vote: disputetypes.VoteEnum_VOTE_SUPPORT}, + }, + expectResult: disputetypes.VoteResult_NO_QUORUM_MAJORITY_SUPPORT, + reporterJailed: true, + selectorLocked: true, + }, + { + name: "no_quorum_majority_against", + votes: []disputetypes.MsgVote{ + {Vote: disputetypes.VoteEnum_VOTE_AGAINST}, + }, + expectResult: disputetypes.VoteResult_NO_QUORUM_MAJORITY_AGAINST, + reporterJailed: false, + selectorLocked: false, + }, + { + name: "no_quorum_majority_invalid", + votes: []disputetypes.MsgVote{ + {Vote: disputetypes.VoteEnum_VOTE_INVALID}, + }, + expectResult: disputetypes.VoteResult_NO_QUORUM_MAJORITY_INVALID, + reporterJailed: false, + selectorLocked: false, + }, + } + + for _, tc := range cases { + s.Run(tc.name, func() { + s.SetupTest() + reporterA, reporterB, _, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + disputeMsgServer := disputekeeper.NewMsgServerImpl(s.Setup.Disputekeeper) + + report, reportBlock := s.submitBridgeReport(reporterA, bridgeQueryData, 10) + s.True(s.snapshotAtReportBlockHasSelector(reporterA, reportBlock, selector)) + + disputer := s.newKeysWithTokens() + disputeID := s.proposeFullMinorDispute(disputer, report) + + // Switch after dispute so we test outcomes while the selector is pending on B. + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(11).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err := msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + d, err := s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + s.NoError(s.Setup.Disputekeeper.SetBlockInfo(s.Setup.Ctx, d.HashId)) + + teamAddr, err := s.Setup.Disputekeeper.GetTeamAddress(s.Setup.Ctx) + s.NoError(err) + var voterAddrs []sdk.AccAddress + if len(tc.votes) == 1 { + voterAddrs = []sdk.AccAddress{teamAddr} + } else { + voterAddrs = []sdk.AccAddress{teamAddr, reporterA, reporterB} + } + for i := range tc.votes { + if i >= len(voterAddrs) { + break + } + d, err := s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + if d.DisputeStatus != disputetypes.Voting { + break // quorum may resolve the dispute before all votes are cast + } + vote := tc.votes[i] + vote.Voter = voterAddrs[i].String() + vote.Id = disputeID + _, err = disputeMsgServer.Vote(s.Setup.Ctx, &vote) + s.NoError(err, "vote %d", i) + } + + // Full minor fee jails on propose; upheld outcomes keep the lock until we check post-execute. + if tc.selectorLocked { + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime()), tc.name) + s.True(s.snapshotAtReportBlockHasSelector(reporterA, reportBlock, selector)) + stakeB, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + valB, err := s.Setup.Stakingkeeper.GetValidator(s.Setup.Ctx, sdk.ValAddress(reporterB)) + s.NoError(err) + s.True(stakeB.Equal(valB.Tokens), "locked selector must not add stake to B during pending switch") + } + + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(disputekeeper.THREE_DAYS + time.Hour)) + s.NoError(dispute.CheckOpenDisputesForExpiration(s.Setup.Ctx, s.Setup.Disputekeeper)) + _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) + s.NoError(err) + + voteInfo, err := s.Setup.Disputekeeper.Votes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + s.True(voteInfo.Executed, tc.name) + s.Equal(tc.expectResult, voteInfo.VoteResult, tc.name) + + repA, err := s.Setup.Reporterkeeper.Reporters.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + effectivelyJailed := repA.Jailed && s.Setup.Ctx.BlockTime().Before(repA.JailedUntil) + if tc.reporterJailed { + s.True(repA.Jailed, tc.name) + } else { + s.False(effectivelyJailed, tc.name) + } + + if !tc.selectorLocked { + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.False(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime()), tc.name) + } + }) + } +} + +// reporterSelfSwitchFixture wires two validator-reporters (A self-reporter, B) with no external selectors. +func (s *IntegrationTestSuite) reporterSelfSwitchFixture() ( + reporterA, reporterB sdk.AccAddress, + bridgeQueryData, queryID []byte, +) { + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + + valAccs, _, _ := s.createValidatorAccs([]uint64{100, 200}) + reporterA, reporterB = valAccs[0], valAccs[1] + + for i, rep := range []sdk.AccAddress{reporterA, reporterB} { + _, err := msgServer.CreateReporter(s.Setup.Ctx, &reportertypes.MsgCreateReporter{ + ReporterAddress: rep.String(), + CommissionRate: reportertypes.DefaultMinCommissionRate, + MinTokensRequired: math.NewIntWithDecimal(1, 6), + Moniker: fmt.Sprintf("self_switch_rep_%d", i), + }) + s.NoError(err) + } + + spec := registrytypes.DataSpec{ + AbiComponents: []*registrytypes.ABIComponent{ + {Name: "tolayer", FieldType: "bool"}, + {Name: "depositId", FieldType: "uint256"}, + }, + } + var err error + bridgeQueryData, err = spec.EncodeData("TRBBridgeV2", `["true","9001"]`) + s.NoError(err) + queryID = utils.QueryIDFromData(bridgeQueryData) + return reporterA, reporterB, bridgeQueryData, queryID +} + +// executeMinorDisputeAgainst drives a minor dispute to an executed reporter-wins (AGAINST) outcome. +// Quorum needs ~51% weighted participation across team, users, and reporters (see dispute keeper +// TallyVote); team alone is only ~33%, so reporter validators must vote with non-zero stake. +// Match TestReporterSwitchMinorDisputeOutcomes: advance one block after propose before +// SetBlockInfo/voting, and include every fixture reporter so reporter-group weight counts. +func (s *IntegrationTestSuite) executeMinorDisputeAgainst( + disputeID uint64, + reporterVoters ...sdk.AccAddress, +) disputetypes.VoteResult { + disputeMsgServer := disputekeeper.NewMsgServerImpl(s.Setup.Disputekeeper) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(s.Setup.Ctx.BlockHeight() + 1). + WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + + d, err := s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + s.NoError(s.Setup.Disputekeeper.SetBlockInfo(s.Setup.Ctx, d.HashId)) + + teamAddr, err := s.Setup.Disputekeeper.GetTeamAddress(s.Setup.Ctx) + s.NoError(err) + voters := append([]sdk.AccAddress{teamAddr}, reporterVoters...) + for _, voter := range voters { + d, err = s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + if d.DisputeStatus != disputetypes.Voting { + break + } + _, err = disputeMsgServer.Vote(s.Setup.Ctx, &disputetypes.MsgVote{ + Voter: voter.String(), + Id: disputeID, + Vote: disputetypes.VoteEnum_VOTE_AGAINST, + }) + s.NoError(err) + } + + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(s.Setup.Ctx.BlockTime().Add(disputekeeper.THREE_DAYS + time.Hour)) + s.NoError(dispute.CheckOpenDisputesForExpiration(s.Setup.Ctx, s.Setup.Disputekeeper)) + _, err = s.Setup.App.BeginBlocker(s.Setup.Ctx) + s.NoError(err) + + voteInfo, err := s.Setup.Disputekeeper.Votes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + s.True(voteInfo.Executed) + s.Equal(disputetypes.VoteResult_AGAINST, voteInfo.VoteResult) + return voteInfo.VoteResult +} + +// TestReporterSwitchFailedDisputePreservesLegacyLock verifies AGAINST clears dispute_locked_until only +// while LockedUntilTime remains; selector stays excluded from B after switch finalize. +// +// 1. Fixture + legacy LockedUntilTime on selector +// 2. Bridge report, minor dispute, vote AGAINST, execute +// 3. Assert dispute lock cleared, legacy lock remains +// 4. Switch to B, finalize at maxCommit+1 +// 5. B stake and report power exclude selector +func (s *IntegrationTestSuite) TestReporterSwitchFailedDisputePreservesLegacyLock() { + reporterA, reporterB, reporterC, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + legacyLock := s.Setup.Ctx.BlockTime().Add(21 * 24 * time.Hour) + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + sel.LockedUntilTime = legacyLock + sel.DisputeLockedUntil = time.Time{} + s.NoError(s.Setup.Reporterkeeper.Selectors.Set(s.Setup.Ctx, selector.Bytes(), sel)) + s.True(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime())) + + bridgeHeight := int64(10) + report, _ := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + + disputeID := s.proposeFullMinorDispute(s.newKeysWithTokens(), report) + // Snapshot reporter C so their vote carries reporter-group weight toward quorum. + _, err = s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterC, queryID) + s.NoError(err) + s.executeMinorDisputeAgainst(disputeID, reporterA, reporterB, reporterC) + + selAfter, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(selAfter.DisputeLockedUntil.Before(s.Setup.Ctx.BlockTime()) || selAfter.DisputeLockedUntil.IsZero()) + s.True(selAfter.LockedUntilTime.Equal(legacyLock)) + s.True(reportertypes.SelectorStakeLocked(selAfter, s.Setup.Ctx.BlockTime())) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(2 * time.Hour)) + stakeBAfter, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBAfter, "legacy LockedUntilTime must keep selector excluded from B after finalize") + + reportHeight := finalizeHeight + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(reportHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + qMetaAfter, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + repB, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMetaAfter.Id)) + s.NoError(err) + s.Equal(stakeBBase.Quo(layertypes.PowerReduction).Uint64(), repB.Power, + "B report power must exclude selector while legacy LockedUntilTime is active") +} + +// TestReporterSwitchSelfDemotionWhileJailed verifies jailed self-reporter demotion copies dispute jail +// to selection, removes the reporter row, and B excludes their stake until unlock. +// +// 1. Self-reporter A and reporter B; A bridge report + minor dispute +// 2. SwitchReporter A→B; Reporters.Has(A) false; selection jail copied +// 3. Pending switch; B stake = solo base through finalize +// 4. After disputeTime+601s, B stake and report power include self delegation +func (s *IntegrationTestSuite) TestReporterSwitchSelfDemotionWhileJailed() { + const minorJailDuration = 600 * time.Second + + reporterA, reporterB, bridgeQueryData, queryID := s.reporterSelfSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + valB, err := s.Setup.Stakingkeeper.GetValidator(s.Setup.Ctx, sdk.ValAddress(reporterB)) + s.NoError(err) + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(valB.Tokens, stakeBBase) + + bridgeHeight := int64(10) + report, _ := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + + disputeTime := s.Setup.Ctx.BlockTime() + s.proposeFullMinorDispute(s.newKeysWithTokens(), report) + + repA, err := s.Setup.Reporterkeeper.Reporters.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.True(repA.Jailed) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(int64(maxCommit) + 1) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: reporterA.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + hasA, err := s.Setup.Reporterkeeper.Reporters.Has(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.False(hasA) + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.True(sel.DisputeLockedUntil.After(disputeTime)) + s.True(sel.LockedUntilTime.IsZero()) + s.True(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime())) + s.True(bytes.Equal(sel.Reporter, reporterA.Bytes()), "selection stays on A until finalize") + + stakeBPending, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBPending) + + maxCommit, err = s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight).WithBlockTime(disputeTime.Add(30 * time.Second)) + stakeBAfterFinalize, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBAfterFinalize) + + selFinal, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selFinal.Reporter, reporterB.Bytes())) + + lockedReportHeight := finalizeHeight + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(lockedReportHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + qMetaAfter, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + repBLocked, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMetaAfter.Id)) + s.NoError(err) + s.Equal(stakeBBase.Quo(layertypes.PowerReduction).Uint64(), repBLocked.Power) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(disputeTime.Add(minorJailDuration + time.Second)) + selUnlocked, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + s.False(reportertypes.SelectorStakeLocked(selUnlocked, s.Setup.Ctx.BlockTime())) + + stakeBUnlocked, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(stakeBUnlocked.GT(stakeBBase)) +} + +// TestReporterSwitchDisputeLockExpiredBeforeFinalize verifies Option A: when dispute lock expires +// before block-height unlock, selector stake counts toward B immediately at finalize. +// +// 1. Fixture + bridge report; switch A→B pending; dispute A's report +// 2. While pending+locked: B stake = base +// 3. Advance wall clock past minor jail only; then maxCommit+1 finalize +// 4. B stake and report power include selector +func (s *IntegrationTestSuite) TestReporterSwitchDisputeLockExpiredBeforeFinalize() { + const minorJailDuration = 600 * time.Second + + reporterA, reporterB, _, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + bridgeHeight := int64(10) + report, _ := s.submitBridgeReport(reporterA, bridgeQueryData, bridgeHeight) + stakeBBase, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + + maxCommit, err := s.Setup.Oraclekeeper.GetMaxOpenCommitmentForReporter(s.Setup.Ctx, reporterA.Bytes()) + s.NoError(err) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(bridgeHeight + 1) + _, err = msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + disputeTime := s.Setup.Ctx.BlockTime() + s.proposeFullMinorDispute(s.newKeysWithTokens(), report) + + stakeBPending, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.Equal(stakeBBase, stakeBPending) + + // Simulate jail ending before the long bridge window closes (accelerated block time in tests). + s.Setup.Ctx = s.Setup.Ctx.WithBlockTime(disputeTime.Add(minorJailDuration + time.Second)) + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.False(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime())) + + finalizeHeight := int64(maxCommit) + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(finalizeHeight) + stakeBAfter, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterB, queryID) + s.NoError(err) + s.True(stakeBAfter.GT(stakeBBase), "selector stake must count toward B at finalize when dispute lock already expired") + + selFinal, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(bytes.Equal(selFinal.Reporter, reporterB.Bytes())) + + reportHeight := finalizeHeight + 1 + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(reportHeight).WithBlockTime(s.Setup.Ctx.BlockTime().Add(time.Second)) + _, err = oracleMsgServer.SubmitValue(s.Setup.Ctx, &oracletypes.MsgSubmitValue{ + Creator: reporterB.String(), + QueryData: bridgeQueryData, + Value: bridgeTestValue, + }) + s.NoError(err) + qMetaAfter, err := s.Setup.Oraclekeeper.CurrentQuery(s.Setup.Ctx, queryID) + s.NoError(err) + repB, err := s.Setup.Oraclekeeper.Reports.Get(s.Setup.Ctx, collections.Join3(queryID, reporterB.Bytes(), qMetaAfter.Id)) + s.NoError(err) + s.Equal(stakeBAfter.Quo(layertypes.PowerReduction).Uint64(), repB.Power) +} + +// TestDisputeVoteSkipsLockedSelectorPendingSwitch verifies dispute-locked selectors with a pending +// switch do not contribute reporter-group stake (SetVoterReporterStake returns 0). MsgVote may still +// fail when team + tips + reporter power sum to zero; it must not fail solely because repP is zero. +func (s *IntegrationTestSuite) TestDisputeVoteSkipsLockedSelectorPendingSwitch() { + s.Run("locked_selector_no_total_power", func() { + s.SetupTest() + reporterA, reporterB, _, selector, bridgeQueryData, _ := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + disputeMsgServer := disputekeeper.NewMsgServerImpl(s.Setup.Disputekeeper) + + report, _ := s.submitBridgeReport(reporterA, bridgeQueryData, 10) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(11) + _, err := msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + disputeID := s.proposeFullMinorDispute(s.newKeysWithTokens(), report) + selAfter, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(reportertypes.SelectorStakeLocked(selAfter, s.Setup.Ctx.BlockTime())) + + d, err := s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + s.NoError(s.Setup.Disputekeeper.SetBlockInfo(s.Setup.Ctx, d.HashId)) + + repP, err := s.Setup.Disputekeeper.SetVoterReporterStake( + s.Setup.Ctx, disputeID, selector, d.BlockNumber, + disputetypes.VoteEnum_VOTE_SUPPORT, nil, + ) + s.NoError(err) + s.True(repP.IsZero(), "locked selector must not contribute reporter-group stake") + + tallyBefore := disputetypes.StakeholderVoteCounts{} + + _, err = disputeMsgServer.Vote(s.Setup.Ctx, &disputetypes.MsgVote{ + Voter: selector.String(), + Id: disputeID, + Vote: disputetypes.VoteEnum_VOTE_SUPPORT, + }) + s.ErrorContains(err, "voter power is zero", + "vote fails when total power is zero, not because reporter power alone is zero") + + tallyAfter := s.disputeTallyOrZero(disputeID) + repBefore, userBefore, teamBefore := disputeTallyTotals(tallyBefore) + repAfter, userAfter, teamAfter := disputeTallyTotals(tallyAfter) + s.Equal(repBefore, repAfter) + s.Equal(userBefore, userAfter) + s.Equal(teamBefore, teamAfter, "failed vote must not change group tallies") + + tallyBefore = tallyAfter + teamAddr, err := s.Setup.Disputekeeper.GetTeamAddress(s.Setup.Ctx) + s.NoError(err) + _, err = disputeMsgServer.Vote(s.Setup.Ctx, &disputetypes.MsgVote{ + Voter: teamAddr.String(), + Id: disputeID, + Vote: disputetypes.VoteEnum_VOTE_SUPPORT, + }) + s.NoError(err) + + tallyAfter = s.disputeTallyOrZero(disputeID) + _, _, teamBefore = disputeTallyTotals(tallyBefore) + _, _, teamAfter = disputeTallyTotals(tallyAfter) + s.Greater(teamAfter, teamBefore, "team vote should increase tallies after failed selector vote") + + hasSelVote, err := s.Setup.Disputekeeper.Voter.Has(s.Setup.Ctx, collections.Join(disputeID, selector.Bytes())) + s.NoError(err) + s.False(hasSelVote) + + hasBVote, err := s.Setup.Disputekeeper.Voter.Has(s.Setup.Ctx, collections.Join(disputeID, reporterB.Bytes())) + s.NoError(err) + s.False(hasBVote, "selector stake must not count toward B reporter vote bucket") + }) + + s.Run("locked_selector_with_tips_succeeds_without_reporter_power", func() { + s.SetupTest() + reporterA, reporterB, _, selector, bridgeQueryData, _ := s.reporterSwitchFixture() + msgServer := reporterkeeper.NewMsgServerImpl(s.Setup.Reporterkeeper) + disputeMsgServer := disputekeeper.NewMsgServerImpl(s.Setup.Disputekeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(s.Setup.Oraclekeeper) + + report, _ := s.submitBridgeReport(reporterA, bridgeQueryData, 10) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(11) + _, err := msgServer.SwitchReporter(s.Setup.Ctx, &reportertypes.MsgSwitchReporter{ + SelectorAddress: selector.String(), + ReporterAddress: reporterB.String(), + }) + s.NoError(err) + + // Tip at the dispute block so user-group power is non-zero while reporter stake stays excluded. + s.Setup.MintTokens(selector, math.NewInt(10_000_000)) + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(12) + _, err = oracleMsgServer.Tip(s.Setup.Ctx, &oracletypes.MsgTip{ + Tipper: selector.String(), + QueryData: bridgeQueryData, + Amount: sdk.NewCoin(s.Setup.Denom, math.NewInt(1_000_000)), + }) + s.NoError(err) + + s.Setup.Ctx = s.Setup.Ctx.WithBlockHeight(12) + disputeID := s.proposeFullMinorDispute(s.newKeysWithTokens(), report) + selAfter, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.True(reportertypes.SelectorStakeLocked(selAfter, s.Setup.Ctx.BlockTime())) + + d, err := s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + s.NoError(s.Setup.Disputekeeper.SetBlockInfo(s.Setup.Ctx, d.HashId)) + + tallyBefore := disputetypes.StakeholderVoteCounts{} + + _, err = disputeMsgServer.Vote(s.Setup.Ctx, &disputetypes.MsgVote{ + Voter: selector.String(), + Id: disputeID, + Vote: disputetypes.VoteEnum_VOTE_SUPPORT, + }) + s.NoError(err, "vote succeeds when tips supply non-zero total power despite zero reporter stake") + + tallyAfter := s.disputeTallyOrZero(disputeID) + repBefore, userBefore, _ := disputeTallyTotals(tallyBefore) + repAfter, userAfter, _ := disputeTallyTotals(tallyAfter) + s.Equal(repBefore, repAfter, "locked selector must not contribute reporter-group tallies") + s.Greater(userAfter, userBefore, "selector tips should count toward user-group tallies") + + tallyBefore = tallyAfter + teamAddr, err := s.Setup.Disputekeeper.GetTeamAddress(s.Setup.Ctx) + s.NoError(err) + _, err = disputeMsgServer.Vote(s.Setup.Ctx, &disputetypes.MsgVote{ + Voter: teamAddr.String(), + Id: disputeID, + Vote: disputetypes.VoteEnum_VOTE_SUPPORT, + }) + s.NoError(err) + + tallyAfter = s.disputeTallyOrZero(disputeID) + repBefore, userBefore, teamBefore := disputeTallyTotals(tallyBefore) + repAfter, userAfter, teamAfter := disputeTallyTotals(tallyAfter) + s.Equal(repBefore, repAfter, "team vote must not change reporter tallies") + s.Equal(userBefore, userAfter, "team vote must not change user tallies from selector vote") + s.Greater(teamAfter, teamBefore, "team vote should increase tallies after selector vote") + + selVote, err := s.Setup.Disputekeeper.Voter.Get(s.Setup.Ctx, collections.Join(disputeID, selector.Bytes())) + s.NoError(err) + s.True(selVote.ReporterPower.IsZero()) + s.True(selVote.VoterPower.IsPositive()) + + hasBVote, err := s.Setup.Disputekeeper.Voter.Has(s.Setup.Ctx, collections.Join(disputeID, reporterB.Bytes())) + s.NoError(err) + s.False(hasBVote) + _ = reporterB + }) + + s.Run("unlocked_selector_contributes_reporter_power", func() { + s.SetupTest() + reporterA, reporterB, reporterC, selector, bridgeQueryData, queryID := s.reporterSwitchFixture() + + report, _ := s.submitBridgeReport(reporterA, bridgeQueryData, 10) + disputeID := s.proposeFullMinorDispute(s.newKeysWithTokens(), report) + _, err := s.Setup.Reporterkeeper.ReporterStake(s.Setup.Ctx, reporterC, queryID) + s.NoError(err) + s.executeMinorDisputeAgainst(disputeID, reporterA, reporterB, reporterC) + + sel, err := s.Setup.Reporterkeeper.Selectors.Get(s.Setup.Ctx, selector.Bytes()) + s.NoError(err) + s.False(reportertypes.SelectorStakeLocked(sel, s.Setup.Ctx.BlockTime())) + + d, err := s.Setup.Disputekeeper.Disputes.Get(s.Setup.Ctx, disputeID) + s.NoError(err) + + repP, err := s.Setup.Disputekeeper.SetVoterReporterStake( + s.Setup.Ctx, disputeID, selector, d.BlockNumber, + disputetypes.VoteEnum_VOTE_SUPPORT, nil, + ) + s.NoError(err) + s.True(repP.IsPositive(), "unlocked selector must contribute reporter-group stake") + }) +} diff --git a/testutil/keeper/reporter.go b/testutil/keeper/reporter.go index eca027e34..1efa9b2ac 100644 --- a/testutil/keeper/reporter.go +++ b/testutil/keeper/reporter.go @@ -5,6 +5,7 @@ import ( cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tellor-io/layer/x/reporter/keeper" "github.com/tellor-io/layer/x/reporter/mocks" @@ -52,6 +53,11 @@ func ReporterKeeper(tb testing.TB) (keeper.Keeper, *mocks.StakingKeeper, *mocks. rk, ) + // Wire up a default OracleKeeper mock: no deferred switch lock from oracle. + ok := new(mocks.OracleKeeper) + ok.On("GetMaxOpenCommitmentForReporter", mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() + k.SetOracleKeeper(ok) + ctx := sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) // Initialize params diff --git a/x/dispute/abci_test.go b/x/dispute/abci_test.go index c2489da49..1f1d52b60 100644 --- a/x/dispute/abci_test.go +++ b/x/dispute/abci_test.go @@ -239,7 +239,7 @@ func BenchmarkDisputeBeginBlocker(b *testing.B) { bk.On("BurnCoins", mock.Anything, mock.Anything, mock.Anything).Return(nil) rk.On("ReturnSlashedTokens", mock.Anything, mock.Anything, mock.Anything).Return(mock.Anything, nil) bk.On("SendCoinsFromModuleToModule", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - rk.On("UpdateJailedUntilOnFailedDispute", mock.Anything, mock.Anything).Return(nil) + rk.On("UpdateJailedUntilOnFailedDispute", mock.Anything, mock.Anything, mock.Anything).Return(nil) // run benchmarks for _, tc := range testCases { diff --git a/x/dispute/keeper/dispute.go b/x/dispute/keeper/dispute.go index c1606b582..fb8d90718 100644 --- a/x/dispute/keeper/dispute.go +++ b/x/dispute/keeper/dispute.go @@ -175,12 +175,11 @@ func (k Keeper) SlashAndJailReporter(ctx sdk.Context, report oracletypes.MicroRe if err != nil { return err } - return k.JailReporter(ctx, reporterAddr, jailDuration) + return k.reporterKeeper.JailReporter(ctx, reporterAddr, jailDuration, report.BlockNumber) } -func (k Keeper) JailReporter(ctx context.Context, repAddr sdk.AccAddress, jailDuration uint64) error { - // noop for major duration, reporter is removed from store so no need to jail - return k.reporterKeeper.JailReporter(ctx, repAddr, jailDuration) +func (k Keeper) JailReporter(ctx context.Context, repAddr sdk.AccAddress, jailDuration, reportBlockNumber uint64) error { + return k.reporterKeeper.JailReporter(ctx, repAddr, jailDuration, reportBlockNumber) } // Get percentage of slash amount based on category, returned as fixed6 diff --git a/x/dispute/keeper/dispute_test.go b/x/dispute/keeper/dispute_test.go index 2029c9cae..14cd65657 100644 --- a/x/dispute/keeper/dispute_test.go +++ b/x/dispute/keeper/dispute_test.go @@ -160,15 +160,15 @@ func (s *KeeperTestSuite) TestSlashAndJailReporter() { dispute := s.dispute(s.ctx) reporterAcc := sdk.MustAccAddressFromBech32(report.Reporter) s.reporterKeeper.On("EscrowReporterStake", s.ctx, reporterAcc, report.Power, uint64(1), math.NewInt(10000), dispute.InitialEvidence.QueryId, dispute.HashId).Return(nil) - s.reporterKeeper.On("JailReporter", s.ctx, reporterAcc, uint64(0)).Return(nil) + s.reporterKeeper.On("JailReporter", s.ctx, reporterAcc, uint64(0), report.BlockNumber).Return(nil) s.oracleKeeper.On("FlagAggregateReport", s.ctx, report).Return(nil) s.NoError(s.disputeKeeper.SlashAndJailReporter(s.ctx, report, dispute.DisputeCategory, dispute.HashId)) } func (s *KeeperTestSuite) TestJailReporter() { reporterAcc := sample.AccAddressBytes() - s.reporterKeeper.On("JailReporter", s.ctx, reporterAcc, uint64(0)).Return(nil) - s.NoError(s.disputeKeeper.JailReporter(s.ctx, reporterAcc, uint64(0))) + s.reporterKeeper.On("JailReporter", s.ctx, reporterAcc, uint64(0), uint64(0)).Return(nil) + s.NoError(s.disputeKeeper.JailReporter(s.ctx, reporterAcc, uint64(0), uint64(0))) } func (s *KeeperTestSuite) TestGetSlashPercentageAndJailDuration() { diff --git a/x/dispute/keeper/execute.go b/x/dispute/keeper/execute.go index d3d9e4875..9cf89218b 100644 --- a/x/dispute/keeper/execute.go +++ b/x/dispute/keeper/execute.go @@ -79,7 +79,7 @@ func (k Keeper) ExecuteVote(ctx context.Context, id uint64) error { if err != nil { return err } - if err := k.reporterKeeper.UpdateJailedUntilOnFailedDispute(ctx, reporterAddr); err != nil { + if err := k.reporterKeeper.UpdateJailedUntilOnFailedDispute(ctx, reporterAddr, dispute.InitialEvidence.BlockNumber); err != nil { return err } vote.Executed = true @@ -116,7 +116,7 @@ func (k Keeper) ExecuteVote(ctx context.Context, id uint64) error { if err != nil { return err } - if err := k.reporterKeeper.UpdateJailedUntilOnFailedDispute(ctx, reporterAddr); err != nil { + if err := k.reporterKeeper.UpdateJailedUntilOnFailedDispute(ctx, reporterAddr, dispute.InitialEvidence.BlockNumber); err != nil { return err } vote.Executed = true diff --git a/x/dispute/keeper/msg_server_add_fee_to_dispute_test.go b/x/dispute/keeper/msg_server_add_fee_to_dispute_test.go index 8d148c5d9..8d36c191e 100644 --- a/x/dispute/keeper/msg_server_add_fee_to_dispute_test.go +++ b/x/dispute/keeper/msg_server_add_fee_to_dispute_test.go @@ -57,7 +57,7 @@ func (k *KeeperTestSuite) TestMsgServerAddFeeToDispute() { k.bankKeeper.On("SendCoinsFromAccountToModule", k.ctx, creator, types.ModuleName, sdk.NewCoins(fee)).Return(nil) k.reporterKeeper.On("EscrowReporterStake", k.ctx, sdk.MustAccAddressFromBech32(dispute.InitialEvidence.Reporter), dispute.InitialEvidence.Power, uint64(1), dispute.SlashAmount, dispute.InitialEvidence.QueryId, dispute.HashId).Return(nil) // jail duration is 0 - k.reporterKeeper.On("JailReporter", k.ctx, sdk.MustAccAddressFromBech32(dispute.InitialEvidence.Reporter), uint64(0)).Return(nil) + k.reporterKeeper.On("JailReporter", k.ctx, sdk.MustAccAddressFromBech32(dispute.InitialEvidence.Reporter), uint64(0), dispute.InitialEvidence.BlockNumber).Return(nil) res, err = k.msgServer.AddFeeToDispute(k.ctx, &msg) k.NoError(err) @@ -128,7 +128,7 @@ func BenchmarkAddFeeToDispute(b *testing.B) { dispute.HashId).Return(nil).Maybe() rk.On("JailReporter", ctx, sdk.MustAccAddressFromBech32(dispute.InitialEvidence.Reporter), - uint64(0)).Return(nil).Maybe() + uint64(0), dispute.InitialEvidence.BlockNumber).Return(nil).Maybe() // reset timer before the benchmark loop b.ResetTimer() diff --git a/x/dispute/keeper/msg_server_propose_dispute_test.go b/x/dispute/keeper/msg_server_propose_dispute_test.go index 39c0fbd2f..da7c3f71c 100644 --- a/x/dispute/keeper/msg_server_propose_dispute_test.go +++ b/x/dispute/keeper/msg_server_propose_dispute_test.go @@ -46,7 +46,7 @@ func (s *KeeperTestSuite) TestMsgProposeDisputeFromAccount() (sdk.AccAddress, ty s.reporterKeeper.On("EscrowReporterStake", s.ctx, addr, uint64(1), uint64(0), math.NewInt(10_000), qId, mock.Anything).Return(nil) s.reporterKeeper.On("TotalReporterPower", s.ctx).Return(math.NewInt(1), nil) s.oracleKeeper.On("GetTotalTips", s.ctx).Return(math.NewInt(1), nil) - s.reporterKeeper.On("JailReporter", s.ctx, addr, uint64(0)).Return(nil) + s.reporterKeeper.On("JailReporter", s.ctx, addr, uint64(0), mock.Anything).Return(nil) s.bankKeeper.On("HasBalance", s.ctx, addr, fee).Return(true) s.bankKeeper.On("SendCoinsFromAccountToModule", s.ctx, addr, mock.Anything, sdk.NewCoins(fee)).Return(nil) @@ -99,7 +99,7 @@ func BenchmarkMsgProposeDispute(b *testing.B) { rk.On("EscrowReporterStake", mock.Anything, addr, uint64(1), uint64(0), math.NewInt(10_000), qId, mock.Anything).Return(nil) rk.On("TotalReporterPower", mock.Anything).Return(math.NewInt(1), nil) ok.On("GetTotalTips", mock.Anything).Return(math.NewInt(1), nil) - rk.On("JailReporter", mock.Anything, addr, uint64(0)).Return(nil) + rk.On("JailReporter", mock.Anything, addr, uint64(0), mock.Anything).Return(nil) bk.On("HasBalance", mock.Anything, addr, fee).Return(true) bk.On("SendCoinsFromAccountToModule", mock.Anything, addr, mock.Anything, sdk.NewCoins(fee)).Return(nil) diff --git a/x/dispute/keeper/msg_server_vote_test.go b/x/dispute/keeper/msg_server_vote_test.go index 8bfcc26c1..3ce5c548a 100644 --- a/x/dispute/keeper/msg_server_vote_test.go +++ b/x/dispute/keeper/msg_server_vote_test.go @@ -116,7 +116,7 @@ func BenchmarkMsgVote(b *testing.B) { reporterKeeper.On("EscrowReporterStake", mock.Anything, addr, uint64(1), uint64(0), math.NewInt(10_000), qId, mock.Anything).Return(nil) reporterKeeper.On("TotalReporterPower", mock.Anything).Return(math.NewInt(1), nil) oracleKeeper.On("GetTotalTips", mock.Anything).Return(math.NewInt(1), nil) - reporterKeeper.On("JailReporter", mock.Anything, addr, uint64(0)).Return(nil) + reporterKeeper.On("JailReporter", mock.Anything, addr, uint64(0), mock.Anything).Return(nil) bankKeeper.On("HasBalance", mock.Anything, addr, fee).Return(true) bankKeeper.On("SendCoinsFromAccountToModule", mock.Anything, addr, mock.Anything, sdk.NewCoins(fee)).Return(nil) oracleKeeper.On("FlagAggregateReport", mock.Anything, report).Return(nil) diff --git a/x/dispute/keeper/vote.go b/x/dispute/keeper/vote.go index 93da055c3..c32855494 100644 --- a/x/dispute/keeper/vote.go +++ b/x/dispute/keeper/vote.go @@ -6,6 +6,7 @@ import ( "errors" "github.com/tellor-io/layer/x/dispute/types" + reportertypes "github.com/tellor-io/layer/x/reporter/types" "cosmossdk.io/collections" "cosmossdk.io/math" @@ -173,14 +174,14 @@ func (k Keeper) SetVoterReporterStake(ctx context.Context, id uint64, voter sdk. } // voter is non-reporter selector // skip selectors that are locked from switching reporters - selector, err := k.reporterKeeper.GetSelector(ctx, voter) + selector, err := k.reporterKeeper.GetSelectorForStake(ctx, voter) if err != nil { if !errors.Is(err, collections.ErrNotFound) { return math.Int{}, err } return math.ZeroInt(), nil } - if selector.LockedUntilTime.After(sdk.UnwrapSDKContext(ctx).BlockTime()) { + if reportertypes.SelectorStakeLocked(selector, sdk.UnwrapSDKContext(ctx).BlockTime()) { return math.ZeroInt(), nil } selectorTokens, err := k.reporterKeeper.GetDelegatorTokensAtBlock(ctx, voter, blockNumber) diff --git a/x/dispute/keeper/vote_test.go b/x/dispute/keeper/vote_test.go index de402a5bb..442d33e6c 100644 --- a/x/dispute/keeper/vote_test.go +++ b/x/dispute/keeper/vote_test.go @@ -297,7 +297,7 @@ func (s *KeeperTestSuite) TestSetVoterReportStake() { }, nil).Once() // selector has 100 selected to reporter rk.On("GetDelegatorTokensAtBlock", ctx, selector.Bytes(), blockNum).Return(math.NewInt(100), nil).Once() - rk.On("GetSelector", ctx, selector).Return(reportertypes.Selection{ + rk.On("GetSelectorForStake", ctx, selector).Return(reportertypes.Selection{ Reporter: reporter, LockedUntilTime: ctx.BlockTime().Add(time.Hour * -24), DelegationsCount: 10, @@ -337,7 +337,7 @@ func (s *KeeperTestSuite) TestSetVoterReportStake() { }, })) require.NoError(k.ReportersWithDelegatorsVotedBefore.Set(ctx, collections.Join(reporter.Bytes(), disputeId), math.NewInt(50))) - rk.On("GetSelector", ctx, selector).Return(reportertypes.Selection{ + rk.On("GetSelectorForStake", ctx, selector).Return(reportertypes.Selection{ Reporter: reporter, LockedUntilTime: ctx.BlockTime().Add(time.Hour * -24), DelegationsCount: 10, @@ -366,7 +366,7 @@ func (s *KeeperTestSuite) TestSetVoterReportStake() { }, nil).Once() // selector has 100 selected to reporter rk.On("GetDelegatorTokensAtBlock", ctx, selector.Bytes(), blockNum).Return(math.NewInt(100), nil).Once() - rk.On("GetSelector", ctx, selector).Return(reportertypes.Selection{ + rk.On("GetSelectorForStake", ctx, selector).Return(reportertypes.Selection{ Reporter: reporter, LockedUntilTime: ctx.BlockTime().Add(time.Hour * 24), DelegationsCount: 10, diff --git a/x/dispute/mocks/ReporterKeeper.go b/x/dispute/mocks/ReporterKeeper.go index 2401b02f6..dc8739336 100644 --- a/x/dispute/mocks/ReporterKeeper.go +++ b/x/dispute/mocks/ReporterKeeper.go @@ -147,8 +147,8 @@ func (_m *ReporterKeeper) GetReporterTokensAtBlock(ctx context.Context, reporter return r0, r1 } -// GetSelector provides a mock function with given fields: ctx, selectorAddr -func (_m *ReporterKeeper) GetSelector(ctx context.Context, selectorAddr types.AccAddress) (reportertypes.Selection, error) { +// GetSelectorForStake provides a mock function with given fields: ctx, selectorAddr +func (_m *ReporterKeeper) GetSelectorForStake(ctx context.Context, selectorAddr types.AccAddress) (reportertypes.Selection, error) { ret := _m.Called(ctx, selectorAddr) var r0 reportertypes.Selection @@ -171,13 +171,13 @@ func (_m *ReporterKeeper) GetSelector(ctx context.Context, selectorAddr types.Ac return r0, r1 } -// JailReporter provides a mock function with given fields: ctx, reporterAddr, jailDuration -func (_m *ReporterKeeper) JailReporter(ctx context.Context, reporterAddr types.AccAddress, jailDuration uint64) error { - ret := _m.Called(ctx, reporterAddr, jailDuration) +// JailReporter provides a mock function with given fields: ctx, reporterAddr, jailDuration, reportBlockNumber +func (_m *ReporterKeeper) JailReporter(ctx context.Context, reporterAddr types.AccAddress, jailDuration uint64, reportBlockNumber uint64) error { + ret := _m.Called(ctx, reporterAddr, jailDuration, reportBlockNumber) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, types.AccAddress, uint64) error); ok { - r0 = rf(ctx, reporterAddr, jailDuration) + if rf, ok := ret.Get(0).(func(context.Context, types.AccAddress, uint64, uint64) error); ok { + r0 = rf(ctx, reporterAddr, jailDuration, reportBlockNumber) } else { r0 = ret.Error(0) } @@ -233,13 +233,13 @@ func (_m *ReporterKeeper) TotalReporterPower(ctx context.Context) (math.Int, err return r0, r1 } -// UpdateJailedUntilOnFailedDispute provides a mock function with given fields: ctx, reporterAddr -func (_m *ReporterKeeper) UpdateJailedUntilOnFailedDispute(ctx context.Context, reporterAddr types.AccAddress) error { - ret := _m.Called(ctx, reporterAddr) +// UpdateJailedUntilOnFailedDispute provides a mock function with given fields: ctx, reporterAddr, reportBlockNumber +func (_m *ReporterKeeper) UpdateJailedUntilOnFailedDispute(ctx context.Context, reporterAddr types.AccAddress, reportBlockNumber uint64) error { + ret := _m.Called(ctx, reporterAddr, reportBlockNumber) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, types.AccAddress) error); ok { - r0 = rf(ctx, reporterAddr) + if rf, ok := ret.Get(0).(func(context.Context, types.AccAddress, uint64) error); ok { + r0 = rf(ctx, reporterAddr, reportBlockNumber) } else { r0 = ret.Error(0) } diff --git a/x/dispute/types/expected_keepers.go b/x/dispute/types/expected_keepers.go index f69a56743..c268a652a 100644 --- a/x/dispute/types/expected_keepers.go +++ b/x/dispute/types/expected_keepers.go @@ -44,7 +44,7 @@ type OracleKeeper interface { type ReporterKeeper interface { EscrowReporterStake(ctx context.Context, reporterAddr sdk.AccAddress, power, height uint64, amt math.Int, queryId, hashId []byte) error - JailReporter(ctx context.Context, reporterAddr sdk.AccAddress, jailDuration uint64) error + JailReporter(ctx context.Context, reporterAddr sdk.AccAddress, jailDuration, reportBlockNumber uint64) error TotalReporterPower(ctx context.Context) (math.Int, error) FeefromReporterStake(ctx context.Context, reporterAddr sdk.AccAddress, amt math.Int, hashId []byte, isFirstRound bool) error ReturnSlashedTokens(ctx context.Context, amt math.Int, hashId []byte) (string, error) @@ -53,6 +53,6 @@ type ReporterKeeper interface { GetReporterTokensAtBlock(ctx context.Context, reporter []byte, blockNumber uint64) (math.Int, error) GetDelegatorTokensAtBlock(ctx context.Context, delegator []byte, blockNumber uint64) (math.Int, error) FeeRefund(ctx context.Context, hashId []byte, amt math.Int) error - UpdateJailedUntilOnFailedDispute(ctx context.Context, reporterAddr sdk.AccAddress) error - GetSelector(ctx context.Context, selectorAddr sdk.AccAddress) (reportertypes.Selection, error) + UpdateJailedUntilOnFailedDispute(ctx context.Context, reporterAddr sdk.AccAddress, reportBlockNumber uint64) error + GetSelectorForStake(ctx context.Context, selectorAddr sdk.AccAddress) (reportertypes.Selection, error) } diff --git a/x/oracle/keeper/aggregate.go b/x/oracle/keeper/aggregate.go index f0be04d6d..e952120e4 100644 --- a/x/oracle/keeper/aggregate.go +++ b/x/oracle/keeper/aggregate.go @@ -108,6 +108,7 @@ func (k Keeper) SetAggregatedReport(ctx context.Context) (err error) { if err != nil { return err } + // cleanup aggregation data that is no longer needed if err = k.cleanupAggregationData(ctx, query.Id); err != nil { return err diff --git a/x/oracle/keeper/keeper.go b/x/oracle/keeper/keeper.go index 32fd277e1..f3458046f 100644 --- a/x/oracle/keeper/keeper.go +++ b/x/oracle/keeper/keeper.go @@ -86,6 +86,10 @@ type ( TotalAggregatesCount collections.Item[uint64] // total aggregates on chain ReporterAggregatesCount collections.Map[[]byte, uint64] // key: reporter, value: reports submitted (incremented at submission) ReporterLastReportTime collections.Map[[]byte, int64] // key: reporter, value: timestamp of last report + // MaxOpenCommitmentByReporter: monotonic max QueryMeta.Expiration seen per + // reporter on each successful SubmitValue; read when scheduling a pending + // reporter switch (unlock height snapshot on the outgoing reporter). + MaxOpenCommitmentByReporter collections.Map[[]byte, uint64] } ) @@ -194,6 +198,12 @@ func NewKeeper( TotalAggregatesCount: collections.NewItem(sb, types.TotalAggregatesCountPrefix, "total_aggregates_count", collections.Uint64Value), ReporterAggregatesCount: collections.NewMap(sb, types.ReporterAggregatesCountPrefix, "reporter_aggregates_count", collections.BytesKey, collections.Uint64Value), ReporterLastReportTime: collections.NewMap(sb, types.ReporterLastReportTimePrefix, "reporter_last_report_time", collections.BytesKey, collections.Int64Value), + MaxOpenCommitmentByReporter: collections.NewMap(sb, + types.MaxOpenCommitmentByReporterPrefix, + "max_open_commitment_by_reporter", + collections.BytesKey, + collections.Uint64Value, + ), } schema, err := sb.Build() diff --git a/x/oracle/keeper/max_open_commitment.go b/x/oracle/keeper/max_open_commitment.go new file mode 100644 index 000000000..dfa127405 --- /dev/null +++ b/x/oracle/keeper/max_open_commitment.go @@ -0,0 +1,35 @@ +package keeper + +import ( + "context" + "errors" + + "cosmossdk.io/collections" +) + +// GetMaxOpenCommitmentForReporter returns the monotonic max query Expiration +// height seen for reports submitted by this reporter (bumped on each MsgSubmitValue). +// Missing entry means 0. The reporter module reads this when scheduling a pending +// switch so unlock_block reflects the outgoing reporter's committed query windows. +func (k Keeper) GetMaxOpenCommitmentForReporter(ctx context.Context, reporter []byte) (uint64, error) { + v, err := k.MaxOpenCommitmentByReporter.Get(ctx, reporter) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return 0, nil + } + return 0, err + } + return v, nil +} + +// bumpMaxOpenCommitmentForReporter raises the stored max when queryExpiration is higher. +func (k Keeper) bumpMaxOpenCommitmentForReporter(ctx context.Context, reporter []byte, queryExpiration uint64) error { + cur, err := k.MaxOpenCommitmentByReporter.Get(ctx, reporter) + if err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + if errors.Is(err, collections.ErrNotFound) || queryExpiration > cur { + return k.MaxOpenCommitmentByReporter.Set(ctx, reporter, queryExpiration) + } + return nil +} diff --git a/x/oracle/keeper/submit_value.go b/x/oracle/keeper/submit_value.go index 97236539f..44ffa7138 100644 --- a/x/oracle/keeper/submit_value.go +++ b/x/oracle/keeper/submit_value.go @@ -106,6 +106,9 @@ func (k Keeper) SetValue(ctx context.Context, reporter sdk.AccAddress, query typ if err := k.Reports.Set(ctx, collections.Join3(queryId, reporter.Bytes(), query.Id), report); err != nil { return err } + if err := k.bumpMaxOpenCommitmentForReporter(ctx, reporter.Bytes(), query.Expiration); err != nil { + return err + } // Track liveness if err := k.TrackReporterParticipation(ctx, reporter.Bytes()); err != nil { diff --git a/x/oracle/types/keys.go b/x/oracle/types/keys.go index 6d20e8611..5a8bc47df 100644 --- a/x/oracle/types/keys.go +++ b/x/oracle/types/keys.go @@ -71,6 +71,9 @@ var ( TotalAggregatesCountPrefix = collections.NewPrefix(43) ReporterAggregatesCountPrefix = collections.NewPrefix(44) ReporterLastReportTimePrefix = collections.NewPrefix(45) + // MaxOpenCommitmentByReporter maps reporter address -> monotonic max query + // Expiration height from reports submitted by that reporter (updated on SubmitValue). + MaxOpenCommitmentByReporterPrefix = collections.NewPrefix(46) ) func KeyPrefix(p string) []byte { diff --git a/x/reporter/keeper/jail.go b/x/reporter/keeper/jail.go index 6331f4e93..ca3be8ab0 100644 --- a/x/reporter/keeper/jail.go +++ b/x/reporter/keeper/jail.go @@ -2,73 +2,316 @@ package keeper import ( "context" + "errors" gomath "math" + "sort" "strconv" "time" "github.com/tellor-io/layer/x/reporter/types" + "cosmossdk.io/collections" + sdk "github.com/cosmos/cosmos-sdk/types" ) -// jail a reporter for a given duration -func (k Keeper) JailReporter(ctx context.Context, reporterAddr sdk.AccAddress, jailDuration uint64) error { - reporter, err := k.Reporters.Get(ctx, reporterAddr) +func maxTime(a, b time.Time) time.Time { + if b.After(a) { + return b + } + return a +} + +func (k Keeper) jailUntil(ctx context.Context, jailDuration uint64) (time.Time, error) { + sdkctx := sdk.UnwrapSDKContext(ctx) + if jailDuration == uint64(gomath.MaxInt64) { + return time.Unix(int64(jailDuration)/1e9, int64(jailDuration)%1e9), nil + } + return sdkctx.BlockTime().Add(time.Second * time.Duration(jailDuration)), nil +} + +// lockSelectorRowDispute extends dispute_locked_until only (never locked_until_time). +func (k Keeper) lockSelectorRowDispute(ctx context.Context, delegator sdk.AccAddress, until time.Time) error { + sel, err := k.Selectors.Get(ctx, delegator.Bytes()) if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return nil + } return err } - if reporter.Jailed { - return types.ErrReporterJailed.Wrapf("cannot jail already jailed reporter, %v", reporter) + now := sdk.UnwrapSDKContext(ctx).BlockTime() + if until.Before(now) { + until = now } - sdkctx := sdk.UnwrapSDKContext(ctx) - if jailDuration == gomath.MaxInt64 { - // Set jailed until to the max time by converting max int64 into seconds and nanoseconds - reporter.JailedUntil = time.Unix(int64(jailDuration)/1e9, int64(jailDuration)%1e9) - } else { - reporter.JailedUntil = sdkctx.BlockTime().Add(time.Second * time.Duration(jailDuration)) - } - reporter.Jailed = true - err = k.Reporters.Set(ctx, reporterAddr, reporter) - if err != nil { + prev := sel.DisputeLockedUntil + sel.DisputeLockedUntil = maxTime(sel.DisputeLockedUntil, until) + if err := k.Selectors.Set(ctx, delegator.Bytes(), sel); err != nil { return err } - sdkctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - "jailed_reporter", - sdk.NewAttribute("reporter", reporterAddr.String()), - sdk.NewAttribute("duration", strconv.FormatUint(jailDuration, 10)), - ), - }) + if !sel.DisputeLockedUntil.After(prev) { + return nil + } + if err := k.flagStakeRecalcForUnlockedSelector(ctx, delegator, sel); err != nil { + return err + } + return k.bumpRecalcAtTimeForSelectorLock(ctx, sdk.AccAddress(sel.Reporter), sel.DisputeLockedUntil) +} + +func (k Keeper) bumpRecalcAtTimeForSelectorLock(ctx context.Context, reporter sdk.AccAddress, lockUntil time.Time) error { + lockUnix := lockUntil.Unix() + existing, err := k.RecalcAtTime.Get(ctx, reporter.Bytes()) + if err != nil { + if !errors.Is(err, collections.ErrNotFound) { + return err + } + return k.RecalcAtTime.Set(ctx, reporter.Bytes(), lockUnix) + } + if lockUnix > existing { + return k.RecalcAtTime.Set(ctx, reporter.Bytes(), lockUnix) + } return nil } -// unjail a reporter -func (k Keeper) UnjailReporter(ctx context.Context, reporterAddr sdk.AccAddress, reporter types.OracleReporter) error { - if !reporter.Jailed { - return types.ErrReporterNotJailed.Wrapf("cannot unjail an already unjailed reporter, %v", reporter.Jailed) +// clearDisputeLock ends an active dispute lock without touching locked_until_time. +func clearDisputeLock(sel *types.Selection, now time.Time) { + if sel.DisputeLockedUntil.After(now) { + sel.DisputeLockedUntil = now.Add(-time.Second) } +} - sdkctx := sdk.UnwrapSDKContext(ctx) - if sdkctx.BlockTime().Before(reporter.JailedUntil) { - return types.ErrReporterJailed.Wrapf("cannot unjail reporter before jail time is up, %v", reporter.JailedUntil) +// flagStakeRecalcForUnlockedSelector flags reporters that should recompute stake after a +// selector lock ends. With an outgoing pending switch, sel.Reporter is still the +// outgoing reporter (stake is held back until finalize); flag both sides of the handoff. +func (k Keeper) flagStakeRecalcForUnlockedSelector(ctx context.Context, selectorAddr sdk.AccAddress, sel types.Selection) error { + hasOutgoing, err := k.hasOutgoingPendingSwitch(ctx, sel.Reporter, selectorAddr.Bytes()) + if err != nil { + return err + } + if hasOutgoing { + entry, err := k.OutgoingPendingSwitches.Get(ctx, collections.Join(sel.Reporter, selectorAddr.Bytes())) + if err != nil { + return err + } + if err := k.FlagStakeRecalc(ctx, sdk.AccAddress(sel.Reporter)); err != nil { + return err + } + return k.FlagStakeRecalc(ctx, sdk.AccAddress(entry.ToReporter)) + } + return k.FlagStakeRecalc(ctx, sdk.AccAddress(sel.Reporter)) +} + +// lazyClearSelectorLocksIfExpired flags stake recalc once a dispute lock has expired while +// legacy locked_until_time may still exclude stake. Reporter rows are never auto-unjailed. +func (k Keeper) lazyClearSelectorLocksIfExpired(ctx context.Context, selectorAddr sdk.AccAddress, sel *types.Selection) error { + if sel.DisputeLockedUntil.IsZero() { + return nil + } + now := sdk.UnwrapSDKContext(ctx).BlockTime() + if types.SelectorStakeLocked(*sel, now) { + return nil } + return k.flagStakeRecalcForUnlockedSelector(ctx, selectorAddr, *sel) +} - reporter.Jailed = false - reporter.JailedUntil = time.Time{} - return k.Reporters.Set(ctx, reporterAddr, reporter) +func (k Keeper) jailSelectorsFromReportSnapshot( + ctx context.Context, + reporter []byte, + reportBlockNumber uint64, + until time.Time, +) error { + snap, err := k.GetDelegationsAmount(ctx, reporter, reportBlockNumber) + if err != nil { + return err + } + seen := make(map[string]struct{}) + delegators := make([]string, 0) + for _, origin := range snap.TokenOrigins { + delegator := sdk.AccAddress(origin.DelegatorAddress) + key := delegator.String() + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + delegators = append(delegators, key) + } + sort.Strings(delegators) + sdkctx := sdk.UnwrapSDKContext(ctx) + for _, key := range delegators { + delegator := sdk.MustAccAddressFromBech32(key) + if err := k.lockSelectorRowDispute(ctx, delegator, until); err != nil { + return err + } + sdkctx.EventManager().EmitEvent(sdk.NewEvent( + "jailed_selector", + sdk.NewAttribute("selector", key), + sdk.NewAttribute("until", until.Format(time.RFC3339)), + )) + } + return nil } -func (k Keeper) UpdateJailedUntilOnFailedDispute(ctx context.Context, reporterAddr sdk.AccAddress) error { - reporter, err := k.Reporters.Get(ctx, reporterAddr) +func (k Keeper) clearSelectorLocksFromReportSnapshot( + ctx context.Context, + reporter []byte, + reportBlockNumber uint64, +) error { + snap, err := k.GetDelegationsAmount(ctx, reporter, reportBlockNumber) if err != nil { return err } + seen := make(map[string]struct{}) + now := sdk.UnwrapSDKContext(ctx).BlockTime() + for _, origin := range snap.TokenOrigins { + delegator := sdk.AccAddress(origin.DelegatorAddress) + if _, ok := seen[delegator.String()]; ok { + continue + } + seen[delegator.String()] = struct{}{} + sel, err := k.Selectors.Get(ctx, delegator.Bytes()) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + continue + } + return err + } + clearDisputeLock(&sel, now) + if err := k.Selectors.Set(ctx, delegator.Bytes(), sel); err != nil { + return err + } + if err := k.flagStakeRecalcForUnlockedSelector(ctx, delegator, sel); err != nil { + return err + } + } + return nil +} + +func (k Keeper) copyReporterJailToSelection(ctx context.Context, addr sdk.AccAddress, reporter types.OracleReporter) error { if !reporter.Jailed { return nil } + return k.lockSelectorRowDispute(ctx, addr, reporter.JailedUntil) +} +// JailReporter jails the reporter row (if present) and every selector in the report snapshot. +// Warning disputes use jailDuration 0: until is block time, so the reporter is jailed but may +// unjail immediately; JailedUntil is only bumped when it is already before block time. +func (k Keeper) JailReporter(ctx context.Context, reporterAddr sdk.AccAddress, jailDuration, reportBlockNumber uint64) error { + until, err := k.jailUntil(ctx, jailDuration) + if err != nil { + return err + } sdkctx := sdk.UnwrapSDKContext(ctx) - reporter.JailedUntil = sdkctx.BlockTime().Add(-1 * time.Second) + now := sdkctx.BlockTime() + if until.Before(now) { + until = now + } + + reporter, err := k.Reporters.Get(ctx, reporterAddr) + if err == nil { + wasJailed := reporter.Jailed + reporter.Jailed = true + reporter.JailedUntil = maxTime(reporter.JailedUntil, until) + if err := k.Reporters.Set(ctx, reporterAddr, reporter); err != nil { + return err + } + if !wasJailed { + sdkctx.EventManager().EmitEvent(sdk.NewEvent( + "jailed_reporter", + sdk.NewAttribute("reporter", reporterAddr.String()), + sdk.NewAttribute("duration", strconv.FormatUint(jailDuration, 10)), + )) + } + } else if !errors.Is(err, collections.ErrNotFound) { + return err + } + + return k.jailSelectorsFromReportSnapshot(ctx, reporterAddr.Bytes(), reportBlockNumber, until) +} + +// thirdPartyUnjailDelay is how long after self-unjail eligibility a third party may unjail. +const thirdPartyUnjailDelay = 7 * 24 * time.Hour + +// UnjailReporter clears jail on the reporter row and/or that address's selection row. +// Self-unjail is allowed once jail/dispute locks expire; third-party unjail requires an +// additional thirdPartyUnjailDelay after that time. +func (k Keeper) UnjailReporter(ctx context.Context, callerAddr, reporterAddr sdk.AccAddress) error { + now := sdk.UnwrapSDKContext(ctx).BlockTime() + selfUnjail := callerAddr.Equals(reporterAddr) + + var reporter types.OracleReporter + hasReporter := false + reporterResult, err := k.Reporters.Get(ctx, reporterAddr) + if err == nil { + reporter = reporterResult + hasReporter = true + } else if !errors.Is(err, collections.ErrNotFound) { + return err + } - return k.Reporters.Set(ctx, reporterAddr, reporter) + var sel types.Selection + hasSelector := false + selResult, err := k.Selectors.Get(ctx, reporterAddr.Bytes()) + if err == nil { + sel = selResult + hasSelector = true + } else if !errors.Is(err, collections.ErrNotFound) { + return err + } + + earliest := time.Time{} + if hasReporter && reporter.Jailed { + earliest = maxTime(earliest, reporter.JailedUntil) + } + if hasSelector && sel.DisputeLockedUntil.After(earliest) { + earliest = sel.DisputeLockedUntil + } + + if selfUnjail { + if now.Before(earliest) { + return types.ErrReporterJailed.Wrapf("cannot unjail before jail time is up, %v", earliest) + } + } else if now.Before(earliest.Add(thirdPartyUnjailDelay)) { + return types.ErrThirdPartyUnjailTooEarly.Wrapf( + "third-party unjail not allowed until %v (7 days after self-unjail eligibility at %v)", + earliest.Add(thirdPartyUnjailDelay), earliest, + ) + } + + hasWork := (hasReporter && reporter.Jailed) || (hasSelector && !sel.DisputeLockedUntil.IsZero()) + if !hasWork { + return types.ErrReporterNotJailed.Wrapf("cannot unjail an already unjailed reporter") + } + + if hasReporter && reporter.Jailed { + reporter.Jailed = false + if err := k.Reporters.Set(ctx, reporterAddr, reporter); err != nil { + return err + } + } + + if hasSelector && !sel.DisputeLockedUntil.IsZero() { + clearDisputeLock(&sel, now) + if err := k.Selectors.Set(ctx, reporterAddr.Bytes(), sel); err != nil { + return err + } + if err := k.flagStakeRecalcForUnlockedSelector(ctx, reporterAddr, sel); err != nil { + return err + } + } + + return nil +} + +func (k Keeper) UpdateJailedUntilOnFailedDispute(ctx context.Context, reporterAddr sdk.AccAddress, reportBlockNumber uint64) error { + reporter, err := k.Reporters.Get(ctx, reporterAddr) + if err == nil && reporter.Jailed { + sdkctx := sdk.UnwrapSDKContext(ctx) + reporter.JailedUntil = sdkctx.BlockTime().Add(-1 * time.Second) + if err := k.Reporters.Set(ctx, reporterAddr, reporter); err != nil { + return err + } + } else if err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + return k.clearSelectorLocksFromReportSnapshot(ctx, reporterAddr.Bytes(), reportBlockNumber) } diff --git a/x/reporter/keeper/jail_test.go b/x/reporter/keeper/jail_test.go index 20ff8180d..aaf556ec3 100644 --- a/x/reporter/keeper/jail_test.go +++ b/x/reporter/keeper/jail_test.go @@ -8,6 +8,7 @@ import ( "github.com/tellor-io/layer/testutil/sample" "github.com/tellor-io/layer/x/reporter/types" + "cosmossdk.io/collections" "cosmossdk.io/math" ) @@ -23,7 +24,7 @@ func TestJailReporter(t *testing.T) { ctx = ctx.WithBlockTime(updatedAt.Add(time.Second * 10)) jailedDuration := uint64(100) - err = k.JailReporter(ctx, addr, jailedDuration) + err = k.JailReporter(ctx, addr, jailedDuration, 1) require.NoError(t, err) ctx = ctx.WithBlockTime(updatedAt.Add(time.Second * 15)) @@ -33,6 +34,39 @@ func TestJailReporter(t *testing.T) { require.Equal(t, updatedAt.Add(time.Second*110), updatedReporter.JailedUntil) } +func TestJailReporterZeroDurationFlagsOnly(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + reporterAddr := sample.AccAddressBytes() + selectorAddr := sample.AccAddressBytes() + reportBlock := uint64(5) + updatedAt := time.Now().UTC() + ctx = ctx.WithBlockTime(updatedAt) + + reporter := types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "reporter_moniker") + require.NoError(t, k.Reporters.Set(ctx, reporterAddr, reporter)) + require.NoError(t, k.Selectors.Set(ctx, selectorAddr, types.NewSelection(reporterAddr, 1))) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporterAddr.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{{DelegatorAddress: selectorAddr}}, + })) + + require.NoError(t, k.JailReporter(ctx, reporterAddr, 0, reportBlock)) + + gotReporter, err := k.Reporters.Get(ctx, reporterAddr) + require.NoError(t, err) + require.True(t, gotReporter.Jailed) + require.Equal(t, updatedAt, gotReporter.JailedUntil) + + gotSelector, err := k.Selectors.Get(ctx, selectorAddr) + require.NoError(t, err) + require.Equal(t, updatedAt, gotSelector.DisputeLockedUntil) + require.True(t, gotSelector.LockedUntilTime.IsZero()) + + require.NoError(t, k.UnjailReporter(ctx, reporterAddr, reporterAddr)) + gotReporter, err = k.Reporters.Get(ctx, reporterAddr) + require.NoError(t, err) + require.False(t, gotReporter.Jailed) +} + func TestUnJailReporter(t *testing.T) { k, _, _, _, _, ctx, _ := setupKeeper(t) addr := sample.AccAddressBytes() @@ -40,41 +74,340 @@ func TestUnJailReporter(t *testing.T) { reporter := types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "reporter_moniker") reporter.Jailed = true reporter.JailedUntil = jailedAt.Add(time.Second * 100) - ctx = ctx.WithBlockTime(jailedAt.Add(time.Second * 50)) + require.NoError(t, k.Reporters.Set(ctx, addr, reporter)) - // test unjailing reporter before the JailedUntil time - err := k.UnjailReporter(ctx, addr, reporter) + ctx = ctx.WithBlockTime(jailedAt.Add(time.Second * 50)) + err := k.UnjailReporter(ctx, addr, addr) require.Error(t, err) - // test unjailing after enough time has passed ctx = ctx.WithBlockTime(jailedAt.Add(time.Second * 505)) - err = k.UnjailReporter(ctx, addr, reporter) + err = k.UnjailReporter(ctx, addr, addr) require.NoError(t, err) updatedReporter, err := k.Reporters.Get(ctx, addr) require.NoError(t, err) require.Equal(t, false, updatedReporter.Jailed) - err = k.UnjailReporter(ctx, addr, updatedReporter) + err = k.UnjailReporter(ctx, addr, addr) require.Error(t, err) } -func TestUpdateJailedUntilOnFailedDispute(t *testing.T) { +func TestThirdPartyUnjailReporter(t *testing.T) { k, _, _, _, _, ctx, _ := setupKeeper(t) - addr := sample.AccAddressBytes() + jailed := sample.AccAddressBytes() + caller := sample.AccAddressBytes() jailedAt := time.Now().UTC() reporter := types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "reporter_moniker") reporter.Jailed = true - reporter.JailedUntil = jailedAt.Add(time.Second * 100) - ctx = ctx.WithBlockTime(jailedAt.Add(time.Second * 50)) - err := k.Reporters.Set(ctx, addr, reporter) + reporter.JailedUntil = jailedAt.Add(100 * time.Second) + require.NoError(t, k.Reporters.Set(ctx, jailed, reporter)) + + // Third party cannot unjail before self-eligibility. + ctx = ctx.WithBlockTime(jailedAt.Add(200 * time.Second)) + err := k.UnjailReporter(ctx, caller, jailed) + require.ErrorIs(t, err, types.ErrThirdPartyUnjailTooEarly) + + // Third party can unjail 7 days after self-eligibility. + ctx = ctx.WithBlockTime(jailedAt.Add(100*time.Second + 7*24*time.Hour)) + require.NoError(t, k.UnjailReporter(ctx, caller, jailed)) + + got, err := k.Reporters.Get(ctx, jailed) require.NoError(t, err) + require.False(t, got.Jailed) +} + +func TestUpdateJailedUntilOnFailedDispute(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + reporterAddr := sample.AccAddressBytes() + selectorA := sample.AccAddressBytes() + selectorB := sample.AccAddressBytes() + reportBlock := uint64(10) + jailedAt := time.Now().UTC() - require.NoError(t, k.UpdateJailedUntilOnFailedDispute(ctx, addr)) + require.NoError(t, k.Reporters.Set(ctx, reporterAddr, types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "reporter"))) + require.NoError(t, k.Selectors.Set(ctx, selectorA, types.NewSelection(reporterAddr, 1))) + require.NoError(t, k.Selectors.Set(ctx, selectorB, types.NewSelection(reporterAddr, 1))) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporterAddr.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{ + {DelegatorAddress: selectorA}, + {DelegatorAddress: selectorB}, + }, + })) - ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Second * 1)) - reporter, err = k.Reporters.Get(ctx, addr) + ctx = ctx.WithBlockTime(jailedAt) + require.NoError(t, k.JailReporter(ctx, reporterAddr, 600, reportBlock)) + + ctx = ctx.WithBlockTime(jailedAt.Add(time.Second * 50)) + require.NoError(t, k.UpdateJailedUntilOnFailedDispute(ctx, reporterAddr, reportBlock)) + + selA, err := k.Selectors.Get(ctx, selectorA) + require.NoError(t, err) + require.True(t, selA.DisputeLockedUntil.Before(ctx.BlockTime())) + require.True(t, selA.LockedUntilTime.IsZero()) + has, err := k.StakeRecalcFlag.Has(ctx, reporterAddr.Bytes()) require.NoError(t, err) + require.True(t, has) + reporter, err := k.Reporters.Get(ctx, reporterAddr) + require.NoError(t, err) require.Equal(t, jailedAt.Add(time.Second*49), reporter.JailedUntil) } + +func TestFailedDisputePreservesLegacyLockedUntilTime(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + reporterAddr := sample.AccAddressBytes() + selector := sample.AccAddressBytes() + reportBlock := uint64(10) + now := time.Now().UTC() + ctx = ctx.WithBlockTime(now) + legacyLock := now.Add(21 * 24 * time.Hour) + + require.NoError(t, k.Reporters.Set(ctx, reporterAddr, types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "reporter"))) + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{ + Reporter: reporterAddr, + LockedUntilTime: legacyLock, + })) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporterAddr.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{{DelegatorAddress: selector}}, + })) + + require.NoError(t, k.JailReporter(ctx, reporterAddr, 600, reportBlock)) + require.NoError(t, k.UpdateJailedUntilOnFailedDispute(ctx, reporterAddr, reportBlock)) + + sel, err := k.Selectors.Get(ctx, selector) + require.NoError(t, err) + require.True(t, sel.DisputeLockedUntil.Before(now)) + require.True(t, sel.LockedUntilTime.Equal(legacyLock)) + require.True(t, types.SelectorStakeLocked(sel, now)) +} + +func TestJailUsesReportByBlockNotReporterIndex(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + reporterR := sample.AccAddressBytes() + reporterT := sample.AccAddressBytes() + selectorA := sample.AccAddressBytes() + selectorC := sample.AccAddressBytes() + reportBlock := uint64(5) + + require.NoError(t, k.Reporters.Set(ctx, reporterR, types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "r"))) + require.NoError(t, k.Selectors.Set(ctx, selectorA, types.NewSelection(reporterT, 1))) + require.NoError(t, k.Selectors.Set(ctx, selectorC, types.NewSelection(reporterR, 1))) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporterR.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{{DelegatorAddress: selectorA}}, + })) + + require.NoError(t, k.JailReporter(ctx, reporterR, 3600, reportBlock)) + + selA, err := k.Selectors.Get(ctx, selectorA) + require.NoError(t, err) + require.True(t, selA.DisputeLockedUntil.After(ctx.BlockTime())) + require.True(t, selA.LockedUntilTime.IsZero()) + + selC, err := k.Selectors.Get(ctx, selectorC) + require.NoError(t, err) + require.True(t, selC.DisputeLockedUntil.IsZero()) +} + +func TestJailReporterLocksSnapshotDelegators(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + reporterR := sample.AccAddressBytes() + selectorA := sample.AccAddressBytes() + selectorB := sample.AccAddressBytes() + reportBlock := uint64(7) + + require.NoError(t, k.Reporters.Set(ctx, reporterR, types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "r"))) + require.NoError(t, k.Selectors.Set(ctx, selectorA, types.NewSelection(reporterR, 1))) + require.NoError(t, k.Selectors.Set(ctx, selectorB, types.NewSelection(reporterR, 1))) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporterR.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{ + {DelegatorAddress: selectorA}, + {DelegatorAddress: selectorB}, + }, + })) + + require.NoError(t, k.JailReporter(ctx, reporterR, 600, reportBlock)) + + for _, sel := range [][]byte{selectorA, selectorB} { + got, err := k.Selectors.Get(ctx, sel) + require.NoError(t, err) + require.True(t, got.DisputeLockedUntil.After(ctx.BlockTime())) + require.True(t, got.LockedUntilTime.IsZero()) + } +} + +func TestJailSetsDisputeLockedUntilOnly(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + selector := sample.AccAddressBytes() + reporter := sample.AccAddressBytes() + reportBlock := uint64(3) + legacyLock := ctx.BlockTime().Add(30 * 24 * time.Hour) + + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{ + Reporter: reporter, + LockedUntilTime: legacyLock, + })) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporter.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{{DelegatorAddress: selector}}, + })) + + require.NoError(t, k.JailReporter(ctx, reporter, 3600, reportBlock)) + + got, err := k.Selectors.Get(ctx, selector) + require.NoError(t, err) + require.True(t, got.DisputeLockedUntil.After(ctx.BlockTime())) + require.True(t, got.LockedUntilTime.Equal(legacyLock)) +} + +func TestJailUsesMaxDisputeLockTime(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + selector := sample.AccAddressBytes() + reporter := sample.AccAddressBytes() + reportBlock := uint64(3) + shorter := ctx.BlockTime().Add(30 * time.Minute) + longer := ctx.BlockTime().Add(2 * time.Hour) + + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{ + Reporter: reporter, + DisputeLockedUntil: shorter, + })) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporter.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{{DelegatorAddress: selector}}, + })) + + require.NoError(t, k.JailReporter(ctx, reporter, 7200, reportBlock)) + + got, err := k.Selectors.Get(ctx, selector) + require.NoError(t, err) + require.True(t, got.DisputeLockedUntil.Equal(longer)) +} + +func TestJailDisputeLockSchedulesRecalc(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + selector := sample.AccAddressBytes() + reporterA := sample.AccAddressBytes() + reporterB := sample.AccAddressBytes() + reportBlock := uint64(3) + + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{Reporter: reporterA})) + require.NoError(t, k.OutgoingPendingSwitches.Set(ctx, collections.Join(reporterA.Bytes(), selector.Bytes()), types.PendingSwitchEntry{ + ToReporter: reporterB.Bytes(), + UnlockBlock: uint64(ctx.BlockHeight()) + 100, + })) + require.NoError(t, k.ReportByBlock.Set(ctx, collections.Join3(reporterA.Bytes(), reportBlock, []byte("q1")), types.DelegationsAmounts{ + TokenOrigins: []*types.TokenOriginInfo{{DelegatorAddress: selector}}, + })) + + require.NoError(t, k.JailReporter(ctx, reporterA, 3600, reportBlock)) + + hasA, err := k.StakeRecalcFlag.Has(ctx, reporterA.Bytes()) + require.NoError(t, err) + require.True(t, hasA) + hasB, err := k.StakeRecalcFlag.Has(ctx, reporterB.Bytes()) + require.NoError(t, err) + require.True(t, hasB) + + recalcAt, err := k.RecalcAtTime.Get(ctx, reporterA.Bytes()) + require.NoError(t, err) + got, err := k.Selectors.Get(ctx, selector) + require.NoError(t, err) + require.Equal(t, got.DisputeLockedUntil.Unix(), recalcAt) +} + +func TestLazyClearSelectorLocksIfExpired(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + selector := sample.AccAddressBytes() + reporter := sample.AccAddressBytes() + now := time.Now().UTC() + ctx = ctx.WithBlockTime(now) + expired := now.Add(-time.Hour) + + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{ + Reporter: reporter, + DisputeLockedUntil: expired, + LockedUntilTime: expired, + })) + + sel, err := k.GetSelectorForStake(ctx, selector) + require.NoError(t, err) + require.Equal(t, expired, sel.DisputeLockedUntil) + require.Equal(t, expired, sel.LockedUntilTime) + require.False(t, types.SelectorStakeLocked(sel, ctx.BlockTime())) + has, err := k.StakeRecalcFlag.Has(ctx, reporter.Bytes()) + require.NoError(t, err) + require.True(t, has) +} + +func TestLazyClearSelectorLocksFlagsRecalcForPendingSwitchTargets(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + selector := sample.AccAddressBytes() + reporterA := sample.AccAddressBytes() + reporterB := sample.AccAddressBytes() + now := time.Now().UTC() + ctx = ctx.WithBlockTime(now) + expired := now.Add(-time.Hour) + + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{ + Reporter: reporterA, + DisputeLockedUntil: expired, + LockedUntilTime: expired, + })) + require.NoError(t, k.OutgoingPendingSwitches.Set(ctx, collections.Join(reporterA.Bytes(), selector.Bytes()), types.PendingSwitchEntry{ + ToReporter: reporterB.Bytes(), + UnlockBlock: uint64(ctx.BlockHeight()) + 100, + })) + + _, err := k.GetSelectorForStake(ctx, selector) + require.NoError(t, err) + + hasA, err := k.StakeRecalcFlag.Has(ctx, reporterA.Bytes()) + require.NoError(t, err) + require.True(t, hasA) + hasB, err := k.StakeRecalcFlag.Has(ctx, reporterB.Bytes()) + require.NoError(t, err) + require.True(t, hasB) +} + +func TestLazyClearSelectorLocksSkipsWhileLockedUntilActive(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + selector := sample.AccAddressBytes() + now := time.Now().UTC() + ctx = ctx.WithBlockTime(now) + + require.NoError(t, k.Selectors.Set(ctx, selector, types.Selection{ + Reporter: sample.AccAddressBytes(), + DisputeLockedUntil: now.Add(-time.Hour), + LockedUntilTime: now.Add(time.Hour), + })) + + sel, err := k.GetSelector(ctx, selector) + require.NoError(t, err) + require.True(t, types.SelectorStakeLocked(sel, now)) + has, err := k.StakeRecalcFlag.Has(ctx, sel.Reporter) + require.NoError(t, err) + require.False(t, has) +} + +func TestUnjailReporterClearsDisputeLockOnly(t *testing.T) { + k, _, _, _, _, ctx, _ := setupKeeper(t) + addr := sample.AccAddressBytes() + until := ctx.BlockTime().Add(time.Hour) + legacyLock := ctx.BlockTime().Add(21 * 24 * time.Hour) + require.NoError(t, k.Reporters.Set(ctx, addr, types.NewReporter(types.DefaultMinCommissionRate, math.OneInt(), "r"))) + require.NoError(t, k.Selectors.Set(ctx, addr, types.Selection{ + Reporter: addr, + DisputeLockedUntil: until, + LockedUntilTime: legacyLock, + })) + + ctx = ctx.WithBlockTime(until.Add(time.Second)) + require.NoError(t, k.UnjailReporter(ctx, addr, addr)) + + sel, err := k.Selectors.Get(ctx, addr) + require.NoError(t, err) + require.True(t, sel.DisputeLockedUntil.Before(ctx.BlockTime())) + require.True(t, sel.LockedUntilTime.Equal(legacyLock)) + require.True(t, types.SelectorStakeLocked(sel, ctx.BlockTime())) + has, err := k.StakeRecalcFlag.Has(ctx, addr.Bytes()) + require.NoError(t, err) + require.True(t, has) +} diff --git a/x/reporter/keeper/keeper.go b/x/reporter/keeper/keeper.go index 64043d442..1ab3e7298 100644 --- a/x/reporter/keeper/keeper.go +++ b/x/reporter/keeper/keeper.go @@ -41,6 +41,13 @@ type ( StakeRecalcFlag collections.Map[[]byte, bool] // reporters flagged for stake recalculation RecalcAtTime collections.Map[[]byte, int64] // per-reporter earliest lock expiry in seconds requiring recalculation + // Deferred reporter switches: key (outgoing_reporter, selector) -> target + unlock height. + OutgoingPendingSwitches collections.Map[collections.Pair[[]byte, []byte], types.PendingSwitchEntry] + // Incoming index: key (incoming_reporter, selector) -> outgoing_reporter address bytes. + IncomingPendingSwitchIdx collections.Map[collections.Pair[[]byte, []byte], []byte] + // One row per reporter: counts + min unlock for O(1) checks in ReporterStake. + ReporterPendingSwitchHeads collections.Map[[]byte, types.ReporterPendingSwitchHead] + Schema collections.Schema logger log.Logger @@ -97,12 +104,30 @@ func NewKeeper( LastValSetUpdateHeight: collections.NewItem(sb, types.LastValSetUpdateHeightPrefix, "last_val_set_update_height", collections.Uint64Value), StakeRecalcFlag: collections.NewMap(sb, types.StakeRecalcFlagPrefix, "stake_recalc_flag", collections.BytesKey, collections.BoolValue), RecalcAtTime: collections.NewMap(sb, types.RecalcAtTimePrefix, "recalc_at_time", collections.BytesKey, collections.Int64Value), - authority: authority, - logger: logger, - accountKeeper: accountKeeper, - stakingKeeper: stakingKeeper, - bankKeeper: bankKeeper, - registryKeeper: registryKeeper, + OutgoingPendingSwitches: collections.NewMap(sb, + types.OutgoingPendingSwitchPrefix, + "outgoing_pending_switch", + collections.PairKeyCodec(collections.BytesKey, collections.BytesKey), + codec.CollValue[types.PendingSwitchEntry](cdc), + ), + IncomingPendingSwitchIdx: collections.NewMap(sb, + types.IncomingPendingSwitchIdxPrefix, + "incoming_pending_switch_idx", + collections.PairKeyCodec(collections.BytesKey, collections.BytesKey), + collections.BytesValue, + ), + ReporterPendingSwitchHeads: collections.NewMap(sb, + types.ReporterPendingSwitchHeadPrefix, + "reporter_pending_switch_heads", + collections.BytesKey, + codec.CollValue[types.ReporterPendingSwitchHead](cdc), + ), + authority: authority, + logger: logger, + accountKeeper: accountKeeper, + stakingKeeper: stakingKeeper, + bankKeeper: bankKeeper, + registryKeeper: registryKeeper, } schema, err := sb.Build() if err != nil { diff --git a/x/reporter/keeper/msg_server.go b/x/reporter/keeper/msg_server.go index 98f97959d..bd98a8a70 100644 --- a/x/reporter/keeper/msg_server.go +++ b/x/reporter/keeper/msg_server.go @@ -12,6 +12,7 @@ import ( layertypes "github.com/tellor-io/layer/types" "github.com/tellor-io/layer/x/reporter/types" + "cosmossdk.io/collections" errorsmod "cosmossdk.io/errors" "cosmossdk.io/math" @@ -77,36 +78,49 @@ func (k msgServer) CreateReporter(goCtx context.Context, msg *types.MsgCreateRep if bytes.Equal(selection.Reporter, addr.Bytes()) { return nil, errors.New("address is already a reporter") } - // check if selector was part of a report before switching - prevReporter := sdk.AccAddress(selection.Reporter) - prevReportedPower, err := k.Keeper.GetReporterTokensAtBlock(goCtx, prevReporter, uint64(sdk.UnwrapSDKContext(goCtx).BlockHeight())) + hasPending, err := k.Keeper.hasOutgoingPendingSwitch(goCtx, selection.Reporter, addr.Bytes()) if err != nil { return nil, err } - if !prevReportedPower.IsZero() { - unbondingTime, err := k.stakingKeeper.UnbondingTime(goCtx) - if err != nil { - return nil, err + prevReporter := sdk.AccAddress(selection.Reporter) + sdkCtx := sdk.UnwrapSDKContext(goCtx) + + if !hasPending { + if selection.SwitchOutLockedUntilBlock >= uint64(sdkCtx.BlockHeight()) && selection.SwitchOutLockedUntilBlock != 0 { + return nil, errors.New("selector is locked until the current reporter switch completes") } - selection.LockedUntilTime = sdk.UnwrapSDKContext(goCtx).BlockTime().Add(unbondingTime) } - selection.Reporter = addr.Bytes() - if err := k.Keeper.Selectors.Set(goCtx, addr.Bytes(), selection); err != nil { + + if err := k.Keeper.scheduleReporterSwitch(goCtx, addr, &selection, prevReporter, addr); err != nil { return nil, err } + if err := k.Keeper.Reporters.Set(goCtx, addr.Bytes(), types.NewReporter(msg.CommissionRate, msg.MinTokensRequired, msg.Moniker)); err != nil { return nil, err } - sdk.UnwrapSDKContext(goCtx).EventManager().EmitEvents(sdk.Events{ + + selAfter, err := k.Keeper.Selectors.Get(goCtx, addr.Bytes()) + if err != nil { + return nil, err + } + maxExp := selAfter.SwitchOutLockedUntilBlock + sdkCtx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( "created_reporter_from_selector", sdk.NewAttribute("reporter", msg.ReporterAddress), sdk.NewAttribute("commission", msg.CommissionRate.String()), sdk.NewAttribute("min_tokens_required", msg.MinTokensRequired.String()), sdk.NewAttribute("moniker", msg.Moniker), + sdk.NewAttribute("pending_switch_lock_until_block", strconv.FormatUint(maxExp, 10)), ), }) - telemetry.IncrCounterWithLabels([]string{"create_reporter_count"}, 1, []metrics.Label{{Name: "chain_id", Value: sdk.UnwrapSDKContext(goCtx).ChainID()}}) + if err := k.Keeper.FlagStakeRecalc(goCtx, prevReporter); err != nil { + return nil, err + } + if err := k.Keeper.FlagStakeRecalc(goCtx, addr); err != nil { + return nil, err + } + telemetry.IncrCounterWithLabels([]string{"create_reporter_count"}, 1, []metrics.Label{{Name: "chain_id", Value: sdkCtx.ChainID()}}) return &types.MsgCreateReporterResponse{}, nil } @@ -225,10 +239,13 @@ func validateSelectReporter(msg *types.MsgSelectReporter) (selector, reporter sd return selector, reporter, nil } -// Msg: SwitchReporter, allows a selector to switch reporters if they meet the new reporters min requirement -// and the new reporter has not reached the max selectors allowed -// switching reporters will not automatically include the selector's tokens to be part of reporting until the unbonding time has passed -// in order to prevent the selector from being part of a report twice unless they were part of a reporter that hasn't reported yet +// Msg: SwitchReporter schedules a move to another reporter: a pending row is stored +// under the outgoing reporter, Selection.reporter stays on the outgoing address until +// unlock height, and ReporterStake (e.g. via MsgSubmitValue) applies the handoff. +// The selector's stake stops counting toward the outgoing reporter immediately; it +// does not count toward the incoming reporter until finalization. Caps, min stake, and +// oracle snapshot unlock (switch_out_locked_until_block) apply when not already +// in-flight for this selector. func (k msgServer) SwitchReporter(goCtx context.Context, msg *types.MsgSwitchReporter) (*types.MsgSwitchReporterResponse, error) { selectorAddr, reporterAddr, err := validateSwitchReporter(msg) if err != nil { @@ -248,20 +265,48 @@ func (k msgServer) SwitchReporter(goCtx context.Context, msg *types.MsgSwitchRep if err != nil { return nil, err } - // check if reporter is trying to become a selector, can only switch if havent reported in the last 21 days + pending, toB, err := k.Keeper.pendingSwitchToReporter(goCtx, prevReporter, selectorAddr) + if err != nil { + return nil, err + } + if pending && bytes.Equal(toB, reporterAddr.Bytes()) { + return &types.MsgSwitchReporterResponse{}, nil + } + // check if reporter is trying to become a selector of another reporter: if they + // still have other selectors, require 21 days since their last oracle report. if bytes.Equal(selector.Reporter, selectorAddr.Bytes()) { - // get the timestamp of the most recent report for reporter switching to selector (msg signer/selector) - lastReportTimestamp, err := k.Keeper.oracleKeeper.GetLastReportedAtTimestamp(goCtx, selectorAddr.Bytes()) + others, err := k.Keeper.CountSelectorsDelegatingToReporterExcludingSelf(goCtx, selectorAddr) if err != nil { return nil, err } + if others > 0 { + lastReportTimestamp, err := k.Keeper.oracleKeeper.GetLastReportedAtTimestamp(goCtx, selectorAddr.Bytes()) + if err != nil { + return nil, err + } + currentBlocktime := uint64(sdk.UnwrapSDKContext(goCtx).BlockTime().UnixMilli()) + if currentBlocktime-lastReportTimestamp < TwentyOneDaysInMs { + return nil, errors.New("reporter has other selectors; must wait 21 days since last report before delegating reporting to another reporter") + } + } - // check if the reporter has reported in the last 21 days - currentBlocktime := uint64(sdk.UnwrapSDKContext(goCtx).BlockTime().UnixMilli()) - if currentBlocktime-lastReportTimestamp < TwentyOneDaysInMs { - return nil, errors.New("reporter has reported in the last 21 days, please wait before switching reporters") + maxCommit, err := k.Keeper.oracleKeeper.GetMaxOpenCommitmentForReporter(goCtx, selectorAddr.Bytes()) + if err != nil { + return nil, err + } + currentBlock := uint64(sdk.UnwrapSDKContext(goCtx).BlockHeight()) + if maxCommit >= currentBlock { + return nil, errors.New("cannot self-demote while reporter has open query commitments; wait until block height exceeds max open commitment height") } + selfRep, selfErr := k.Keeper.Reporters.Get(goCtx, selectorAddr.Bytes()) + if selfErr == nil && selfRep.Jailed { + if err := k.Keeper.copyReporterJailToSelection(goCtx, selectorAddr, selfRep); err != nil { + return nil, err + } + } else if selfErr != nil && !errors.Is(selfErr, collections.ErrNotFound) { + return nil, selfErr + } if err := k.Keeper.Reporters.Remove(goCtx, selectorAddr.Bytes()); err != nil { return nil, err } @@ -291,45 +336,46 @@ func (k msgServer) SwitchReporter(goCtx context.Context, msg *types.MsgSwitchRep return nil, fmt.Errorf("reporter's min requirement of %s not met by selector. Must stake enough to reach the minimum", reporter.MinTokensRequired.String()) } - // check if selector was part of a report before switching - prevReportedPower, err := k.Keeper.GetReporterTokensAtBlock(goCtx, prevReporter, uint64(sdk.UnwrapSDKContext(goCtx).BlockHeight())) + sdkCtx := sdk.UnwrapSDKContext(goCtx) + currentBlock := uint64(sdkCtx.BlockHeight()) + + hasPending, err := k.Keeper.hasOutgoingPendingSwitch(goCtx, prevReporter.Bytes(), selectorAddr.Bytes()) if err != nil { return nil, err } - - if !prevReportedPower.IsZero() { - unbondingTime, err := k.stakingKeeper.UnbondingTime(goCtx) - if err != nil { - return nil, err + if !hasPending { + if selector.SwitchOutLockedUntilBlock >= currentBlock && selector.SwitchOutLockedUntilBlock != 0 { + return nil, errors.New("selector is locked until the current reporter switch completes") } + } - selector.LockedUntilTime = sdk.UnwrapSDKContext(goCtx).BlockTime().Add(unbondingTime) + // Original reporter must recompute stake immediately so the selector's power + // is excluded from future reports while the switch is pending. + if err := k.Keeper.FlagStakeRecalc(goCtx, prevReporter); err != nil { + return nil, err + } - // Set RecalcAtTime for the new reporter so their cache is updated when this lock expires. - lockUnix := selector.LockedUntilTime.Unix() - existing, err := k.Keeper.RecalcAtTime.Get(goCtx, reporterAddr.Bytes()) - if err != nil || lockUnix < existing { - if err := k.Keeper.RecalcAtTime.Set(goCtx, reporterAddr.Bytes(), lockUnix); err != nil { - return nil, err - } - } + if err := k.Keeper.lazyClearSelectorLocksIfExpired(goCtx, selectorAddr, &selector); err != nil { + return nil, err } - selector.Reporter = reporterAddr.Bytes() - if err := k.Keeper.Selectors.Set(goCtx, selectorAddr.Bytes(), selector); err != nil { + if err := k.Keeper.scheduleReporterSwitch(goCtx, selectorAddr, &selector, prevReporter, reporterAddr); err != nil { return nil, err } - sdk.UnwrapSDKContext(goCtx).EventManager().EmitEvents(sdk.Events{ + + selAfter, err := k.Keeper.Selectors.Get(goCtx, selectorAddr.Bytes()) + if err != nil { + return nil, err + } + maxExp := selAfter.SwitchOutLockedUntilBlock + sdkCtx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( "switched_reporter", sdk.NewAttribute("selector", msg.SelectorAddress), sdk.NewAttribute("previous_reporter", prevReporter.String()), sdk.NewAttribute("new_reporter", msg.ReporterAddress), - sdk.NewAttribute("selector_locked_until", selector.LockedUntilTime.String()), + sdk.NewAttribute("pending_switch_lock_until_block", strconv.FormatUint(maxExp, 10)), ), }) - if err := k.Keeper.FlagStakeRecalc(goCtx, prevReporter); err != nil { - return nil, err - } if err := k.Keeper.FlagStakeRecalc(goCtx, reporterAddr); err != nil { return nil, err } @@ -427,32 +473,42 @@ func validateRemoveSelector(msg *types.MsgRemoveSelector) (selector sdk.AccAddre return selector, nil } -// Msg: UnjailReporter, allows a reporter that is jailed to be unjailed if the jail period has passed (jail period is set during a dispute) +// Msg: UnjailReporter allows a jailed reporter or selector to be unjailed after their +// sentence. The reporter may unjail themselves once eligible; any account may unjail them +// seven days after that. func (k msgServer) UnjailReporter(goCtx context.Context, msg *types.MsgUnjailReporter) (*types.MsgUnjailReporterResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - reporterAddr, err := sdk.AccAddressFromBech32(msg.ReporterAddress) - if err != nil { - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid reporter address (%s)", err) - } - - reporter, err := k.Reporters.Get(ctx, reporterAddr) + callerAddr, reporterAddr, err := validateUnjailReporter(msg) if err != nil { return nil, err } - if err := k.Keeper.UnjailReporter(ctx, reporterAddr, reporter); err != nil { + if err := k.Keeper.UnjailReporter(ctx, callerAddr, reporterAddr); err != nil { return nil, err } ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( "unjailed_reporter", sdk.NewAttribute("reporter", reporterAddr.String()), + sdk.NewAttribute("caller", callerAddr.String()), ), }) return &types.MsgUnjailReporterResponse{}, nil } +func validateUnjailReporter(msg *types.MsgUnjailReporter) (caller, reporter sdk.AccAddress, err error) { + caller, err = sdk.AccAddressFromBech32(msg.SignerAddress) + if err != nil { + return nil, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid signer address (%s)", err) + } + reporter, err = sdk.AccAddressFromBech32(msg.ReporterAddress) + if err != nil { + return nil, nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid reporter address (%s)", err) + } + return caller, reporter, nil +} + // Msg: WithdrawTip, allows selectors to directly withdraw reporting rewards and stake them with a BONDED validator func (k msgServer) WithdrawTip(goCtx context.Context, msg *types.MsgWithdrawTip) (*types.MsgWithdrawTipResponse, error) { selectorAddr, err := validateWithdrawTip(msg) diff --git a/x/reporter/keeper/msg_server_test.go b/x/reporter/keeper/msg_server_test.go index a0bd22185..070f611f8 100644 --- a/x/reporter/keeper/msg_server_test.go +++ b/x/reporter/keeper/msg_server_test.go @@ -136,6 +136,7 @@ func TestSwitchReporter(t *testing.T) { selector, reporter, reporter2 := sample.AccAddressBytes(), sample.AccAddressBytes(), sample.AccAddressBytes() require.NoError(t, k.Selectors.Set(ctx, selector, types.NewSelection(reporter, 1))) + require.NoError(t, k.Reporters.Set(ctx, reporter, types.NewReporter(types.DefaultMinCommissionRate, types.DefaultMinLoya, "r1"))) // reporter2 does not exist _, err := ms.SwitchReporter(ctx, &types.MsgSwitchReporter{SelectorAddress: selector.String(), ReporterAddress: reporter2.String()}) require.ErrorIs(t, err, collections.ErrNotFound) @@ -182,36 +183,82 @@ func TestSwitchReporter(t *testing.T) { selection, err := k.Selectors.Get(ctx, selector) require.NoError(t, err) - require.True(t, bytes.Equal(reporter2.Bytes(), selection.Reporter)) + require.True(t, bytes.Equal(reporter.Bytes(), selection.Reporter)) + pk := collections.Join(reporter.Bytes(), selector.Bytes()) + has, err := k.OutgoingPendingSwitches.Has(ctx, pk) + require.NoError(t, err) + require.True(t, has) + ent, err := k.OutgoingPendingSwitches.Get(ctx, pk) + require.NoError(t, err) + require.True(t, bytes.Equal(reporter2.Bytes(), ent.ToReporter)) require.True(t, selection.LockedUntilTime.IsZero()) + ctx = ctx.WithBlockHeight(2) + _, err = k.ReporterStake(ctx, reporter, []byte{}) + require.NoError(t, err) + + selection, err = k.Selectors.Get(ctx, selector) + require.NoError(t, err) + require.True(t, bytes.Equal(reporter2.Bytes(), selection.Reporter)) + _, err = ms.SwitchReporter(ctx, &types.MsgSwitchReporter{SelectorAddress: selector.String(), ReporterAddress: reporter2.String()}) require.ErrorContains(t, err, "selector is already assigned to this reporter") +} - // reset reporter for selector - require.NoError(t, k.Selectors.Set(ctx, selector, types.NewSelection(reporter, 1))) +func TestSwitchReporterReplacesPendingTargetKeepsUnlock(t *testing.T) { + k, sk, _, _, _, ms, ctx := setupMsgServer(t) + ctx = ctx.WithBlockTime(time.Now()).WithBlockHeight(10) + sel, r1, r2, r3 := sample.AccAddressBytes(), sample.AccAddressBytes(), sample.AccAddressBytes(), sample.AccAddressBytes() - // this time selector was part of previous reporting - tokenOrigin := &types.TokenOriginInfo{ - DelegatorAddress: selector.Bytes(), - ValidatorAddress: selector.Bytes(), - Amount: math.NewInt(1000 * 1e6), - } - tokenOrigins := []*types.TokenOriginInfo{tokenOrigin} + require.NoError(t, k.Selectors.Set(ctx, sel, types.NewSelection(r1, 1))) + require.NoError(t, k.Reporters.Set(ctx, r1, types.NewReporter(types.DefaultMinCommissionRate, types.DefaultMinLoya, "r1"))) + require.NoError(t, k.Reporters.Set(ctx, r2, types.NewReporter(types.DefaultMinCommissionRate, types.DefaultMinLoya, "r2"))) + require.NoError(t, k.Reporters.Set(ctx, r3, types.NewReporter(types.DefaultMinCommissionRate, types.DefaultMinLoya, "r3"))) + require.NoError(t, k.Params.Set(ctx, types.Params{MaxSelectors: 10})) + + sk.On("IterateDelegatorDelegations", ctx, sel, mock.AnythingOfType("func(types.Delegation) bool")).Return(nil).Maybe().Run(func(args mock.Arguments) { + fn := args.Get(2).(func(stakingtypes.Delegation) bool) + delegations := []stakingtypes.Delegation{ + { + DelegatorAddress: sel.String(), + ValidatorAddress: sdk.ValAddress(sel).String(), + Shares: math.LegacyNewDec(1000), + }, + } + for _, delegation := range delegations { + val := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(sel).String(), + Status: stakingtypes.Bonded, + Tokens: math.NewInt(1_000_000), + DelegatorShares: math.LegacyNewDec(1_000), + } + sk.On("GetValidator", ctx, sdk.ValAddress(sel)).Return(val, nil) + if fn(delegation) { + break + } + } + }) - delegationAmounts := types.DelegationsAmounts{TokenOrigins: tokenOrigins, Total: math.NewInt(1000 * 1e6)} - require.NoError(t, k.Report.Set(ctx, collections.Join([]byte{}, collections.Join(reporter.Bytes(), uint64(ctx.BlockHeight()))), delegationAmounts)) + _, err := ms.SwitchReporter(ctx, &types.MsgSwitchReporter{SelectorAddress: sel.String(), ReporterAddress: r2.String()}) + require.NoError(t, err) + pk := collections.Join(r1.Bytes(), sel.Bytes()) + ent1, err := k.OutgoingPendingSwitches.Get(ctx, pk) + require.NoError(t, err) + unlock := ent1.UnlockBlock - // rk.On("MaxReportBufferWindow", ctx).Return(700_000, nil) - sk.On("UnbondingTime", ctx).Return(1814400*time.Second, nil) - _, err = ms.SwitchReporter(ctx, &types.MsgSwitchReporter{SelectorAddress: selector.String(), ReporterAddress: reporter2.String()}) + _, err = ms.SwitchReporter(ctx, &types.MsgSwitchReporter{SelectorAddress: sel.String(), ReporterAddress: r3.String()}) + require.NoError(t, err) + ent2, err := k.OutgoingPendingSwitches.Get(ctx, pk) require.NoError(t, err) + require.Equal(t, unlock, ent2.UnlockBlock) + require.True(t, bytes.Equal(r3.Bytes(), ent2.ToReporter)) - selection, err = k.Selectors.Get(ctx, selector) + hasOld, err := k.IncomingPendingSwitchIdx.Has(ctx, collections.Join(r2.Bytes(), sel.Bytes())) require.NoError(t, err) - require.True(t, bytes.Equal(reporter2.Bytes(), selection.Reporter)) - require.False(t, selection.LockedUntilTime.IsZero()) - require.Equal(t, selection.LockedUntilTime, ctx.BlockTime().Add(1814400*time.Second)) + require.False(t, hasOld) + hasNew, err := k.IncomingPendingSwitchIdx.Has(ctx, collections.Join(r3.Bytes(), sel.Bytes())) + require.NoError(t, err) + require.True(t, hasNew) } func TestSwitchReporterRejectsSelfSwitchAfterSelectingAndCreatingReporter(t *testing.T) { @@ -269,6 +316,18 @@ func TestSwitchReporterRejectsSelfSwitchAfterSelectingAndCreatingReporter(t *tes selection, err := k.Selectors.Get(ctx, selector) require.NoError(t, err) + require.True(t, bytes.Equal(initialReporter.Bytes(), selection.Reporter)) + pk := collections.Join(initialReporter.Bytes(), selector.Bytes()) + has, err := k.OutgoingPendingSwitches.Has(ctx, pk) + require.NoError(t, err) + require.True(t, has) + + ctx = ctx.WithBlockHeight(2) + _, err = k.ReporterStake(ctx, initialReporter, []byte{}) + require.NoError(t, err) + + selection, err = k.Selectors.Get(ctx, selector) + require.NoError(t, err) require.True(t, bytes.Equal(selector.Bytes(), selection.Reporter)) _, err = k.Reporters.Get(ctx, selector) @@ -336,18 +395,18 @@ func TestUnjailReporter(t *testing.T) { reporter, err := k.Reporters.Get(ctx, addr) require.NoError(t, err) require.False(t, reporter.Jailed) - _, err = msg.UnjailReporter(ctx, &types.MsgUnjailReporter{ReporterAddress: addr.String()}) - require.ErrorContains(t, err, "cannot unjail an already unjailed reporter, false: reporter not jailed") + _, err = msg.UnjailReporter(ctx, &types.MsgUnjailReporter{SignerAddress: addr.String(), ReporterAddress: addr.String()}) + require.ErrorContains(t, err, "cannot unjail an already unjailed reporter") reporter.Jailed = true reporter.JailedUntil = ctx.BlockTime().Add(time.Hour) require.NoError(t, k.Reporters.Set(ctx, addr, reporter)) - _, err = msg.UnjailReporter(ctx, &types.MsgUnjailReporter{ReporterAddress: addr.String()}) - require.ErrorContains(t, err, "cannot unjail reporter before jail time is up") + _, err = msg.UnjailReporter(ctx, &types.MsgUnjailReporter{SignerAddress: addr.String(), ReporterAddress: addr.String()}) + require.ErrorContains(t, err, "cannot unjail before jail time is up") ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Hour)) - _, err = msg.UnjailReporter(ctx, &types.MsgUnjailReporter{ReporterAddress: addr.String()}) + _, err = msg.UnjailReporter(ctx, &types.MsgUnjailReporter{SignerAddress: addr.String(), ReporterAddress: addr.String()}) require.NoError(t, err) reporter, err = k.Reporters.Get(ctx, addr) @@ -600,6 +659,7 @@ func BenchmarkUnjailReporter(b *testing.B) { ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Hour)) msg := &types.MsgUnjailReporter{ + SignerAddress: addr.String(), ReporterAddress: addr.String(), } diff --git a/x/reporter/keeper/msg_update_params_test.go b/x/reporter/keeper/msg_update_params_test.go index b98ddad91..54d689c8d 100644 --- a/x/reporter/keeper/msg_update_params_test.go +++ b/x/reporter/keeper/msg_update_params_test.go @@ -35,7 +35,7 @@ func TestMsgUpdateParams(t *testing.T) { name: "send enabled param", input: &types.MsgUpdateParams{ Authority: k.GetAuthority(), - Params: types.Params{}, + Params: types.DefaultParams(), }, expErr: false, }, diff --git a/x/reporter/keeper/pending_switch.go b/x/reporter/keeper/pending_switch.go new file mode 100644 index 000000000..3f0336d13 --- /dev/null +++ b/x/reporter/keeper/pending_switch.go @@ -0,0 +1,392 @@ +package keeper + +import ( + "bytes" + "context" + "errors" + + "github.com/tellor-io/layer/x/reporter/types" + + "cosmossdk.io/collections" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// applyReadyPendingSwitchesForReporter finalizes any pending switches involving +// this reporter that are strictly past unlock_block. It is invoked at the start +// of ReporterStake so MsgSubmitValue triggers handoffs without a BeginBlocker. +// A single ReporterPendingSwitchHead Get is used to short-circuit when nothing +// could be ready at the current height. +func (k Keeper) applyReadyPendingSwitchesForReporter(ctx context.Context, repAddr sdk.AccAddress) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + h := uint64(sdkCtx.BlockHeight()) + + head, err := k.ReporterPendingSwitchHeads.Get(ctx, repAddr.Bytes()) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return nil + } + return err + } + + outReady := head.OutgoingCount > 0 && h > head.OutgoingMinUnlock + inReady := head.IncomingCount > 0 && h > head.IncomingMinUnlock + if !outReady && !inReady { + return nil + } + + if outReady { + if err := k.applyReadyOutgoingPendingForReporter(ctx, repAddr.Bytes(), h); err != nil { + return err + } + } + if inReady { + if err := k.applyReadyIncomingPendingForReporter(ctx, repAddr.Bytes(), h); err != nil { + return err + } + } + return nil +} + +func (k Keeper) applyReadyOutgoingPendingForReporter(ctx context.Context, from []byte, h uint64) error { + rng := collections.NewPrefixedPairRange[[]byte, []byte](from) + iter, err := k.OutgoingPendingSwitches.Iterate(ctx, rng) + if err != nil { + return err + } + defer iter.Close() + + var keys []collections.Pair[[]byte, []byte] + for ; iter.Valid(); iter.Next() { + pk, err := iter.Key() + if err != nil { + return err + } + val, err := iter.Value() + if err != nil { + return err + } + if val.UnlockBlock < h { + keys = append(keys, pk) + } + } + for _, pk := range keys { + if err := k.finalizePendingSwitch(ctx, pk.K1(), pk.K2()); err != nil { + return err + } + } + return nil +} + +func (k Keeper) applyReadyIncomingPendingForReporter(ctx context.Context, to []byte, h uint64) error { + rng := collections.NewPrefixedPairRange[[]byte, []byte](to) + iter, err := k.IncomingPendingSwitchIdx.Iterate(ctx, rng) + if err != nil { + return err + } + defer iter.Close() + + var keys []collections.Pair[[]byte, []byte] + for ; iter.Valid(); iter.Next() { + pk, err := iter.Key() + if err != nil { + return err + } + from, err := iter.Value() + if err != nil { + return err + } + outK := collections.Join(from, pk.K2()) + val, err := k.OutgoingPendingSwitches.Get(ctx, outK) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + continue + } + return err + } + if val.UnlockBlock < h { + keys = append(keys, outK) + } + } + for _, outK := range keys { + if err := k.finalizePendingSwitch(ctx, outK.K1(), outK.K2()); err != nil { + return err + } + } + return nil +} + +func (k Keeper) finalizePendingSwitch(ctx context.Context, from, selector []byte) error { + outK := collections.Join(from, selector) + entry, err := k.OutgoingPendingSwitches.Get(ctx, outK) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return nil + } + return err + } + to := entry.ToReporter + inK := collections.Join(to, selector) + + sel, err := k.Selectors.Get(ctx, selector) + if err != nil { + return err + } + if !bytes.Equal(sel.Reporter, from) { + // stale row; clean up + _ = k.OutgoingPendingSwitches.Remove(ctx, outK) + _ = k.IncomingPendingSwitchIdx.Remove(ctx, inK) + if err := k.recomputeReporterPendingSwitchHead(ctx, from); err != nil { + return err + } + return k.recomputeReporterPendingSwitchHead(ctx, to) + } + + sel.Reporter = append([]byte(nil), to...) + sel.SwitchOutLockedUntilBlock = 0 + if err := k.Selectors.Set(ctx, selector, sel); err != nil { + return err + } + if err := k.OutgoingPendingSwitches.Remove(ctx, outK); err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + if err := k.IncomingPendingSwitchIdx.Remove(ctx, inK); err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + if err := k.recomputeReporterPendingSwitchHead(ctx, from); err != nil { + return err + } + if err := k.recomputeReporterPendingSwitchHead(ctx, to); err != nil { + return err + } + if err := k.FlagStakeRecalc(ctx, sdk.AccAddress(from)); err != nil { + return err + } + return k.FlagStakeRecalc(ctx, sdk.AccAddress(to)) +} + +// removeOutgoingPendingSwitch deletes a scheduled switch (from, selector) → oldTo +// and refreshes heads. Does not change Selection. +func (k Keeper) removeOutgoingPendingSwitch(ctx context.Context, from, selector, oldTo []byte) error { + outK := collections.Join(from, selector) + inK := collections.Join(oldTo, selector) + if err := k.OutgoingPendingSwitches.Remove(ctx, outK); err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + if err := k.IncomingPendingSwitchIdx.Remove(ctx, inK); err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + if err := k.recomputeReporterPendingSwitchHead(ctx, from); err != nil { + return err + } + return k.recomputeReporterPendingSwitchHead(ctx, oldTo) +} + +func (k Keeper) recomputeReporterPendingSwitchHead(ctx context.Context, rep []byte) error { + var head types.ReporterPendingSwitchHead + + outRng := collections.NewPrefixedPairRange[[]byte, []byte](rep) + outIter, err := k.OutgoingPendingSwitches.Iterate(ctx, outRng) + if err != nil { + return err + } + var outMin uint64 + var outCnt uint32 + firstOut := true + for ; outIter.Valid(); outIter.Next() { + val, err := outIter.Value() + if err != nil { + outIter.Close() + return err + } + outCnt++ + if firstOut || val.UnlockBlock < outMin { + outMin = val.UnlockBlock + firstOut = false + } + } + outIter.Close() + head.OutgoingCount = outCnt + head.OutgoingMinUnlock = outMin + + inRng := collections.NewPrefixedPairRange[[]byte, []byte](rep) + inIter, err := k.IncomingPendingSwitchIdx.Iterate(ctx, inRng) + if err != nil { + return err + } + var inMin uint64 + var inCnt uint32 + firstIn := true + for ; inIter.Valid(); inIter.Next() { + pk, err := inIter.Key() + if err != nil { + inIter.Close() + return err + } + from, err := inIter.Value() + if err != nil { + inIter.Close() + return err + } + ev, err := k.OutgoingPendingSwitches.Get(ctx, collections.Join(from, pk.K2())) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + continue + } + inIter.Close() + return err + } + inCnt++ + if firstIn || ev.UnlockBlock < inMin { + inMin = ev.UnlockBlock + firstIn = false + } + } + inIter.Close() + head.IncomingCount = inCnt + head.IncomingMinUnlock = inMin + + if head.OutgoingCount == 0 && head.IncomingCount == 0 { + return k.ReporterPendingSwitchHeads.Remove(ctx, rep) + } + return k.ReporterPendingSwitchHeads.Set(ctx, rep, head) +} + +func (k Keeper) scheduleReporterSwitch( + ctx context.Context, + selectorAddr sdk.AccAddress, + selection *types.Selection, + prevReporter, newReporter sdk.AccAddress, +) error { + if k.oracleKeeper == nil { + return errors.New("oracle keeper not configured") + } + params, err := k.Params.Get(ctx) + if err != nil { + return err + } + maxP := params.MaxPendingSwitchesPerReporter + if maxP == 0 { + maxP = types.DefaultMaxPendingSwitchesPerReporter + } + + outK := collections.Join(prevReporter.Bytes(), selectorAddr.Bytes()) + existing, err := k.OutgoingPendingSwitches.Get(ctx, outK) + wasReplace := err == nil + if err != nil && !errors.Is(err, collections.ErrNotFound) { + return err + } + if wasReplace { + if bytes.Equal(existing.ToReporter, newReporter.Bytes()) { + return nil + } + if err := k.removeOutgoingPendingSwitch(ctx, prevReporter.Bytes(), selectorAddr.Bytes(), existing.ToReporter); err != nil { + return err + } + } + + if !wasReplace { + prevHead, _ := k.reporterPendingSwitchHeadOrZero(ctx, prevReporter.Bytes()) + if uint64(prevHead.OutgoingCount) >= maxP { + return errors.New("outgoing reporter has reached max pending reporter switches") + } + } + + newHead, _ := k.reporterPendingSwitchHeadOrZero(ctx, newReporter.Bytes()) + if uint64(newHead.IncomingCount) >= maxP { + return errors.New("target reporter has reached max pending incoming reporter switches") + } + + var unlockBlock uint64 + if wasReplace { + unlockBlock = existing.UnlockBlock + } else { + unlockBlock, err = k.oracleKeeper.GetMaxOpenCommitmentForReporter(ctx, prevReporter.Bytes()) + if err != nil { + return err + } + } + + entry := types.PendingSwitchEntry{ + ToReporter: newReporter.Bytes(), + UnlockBlock: unlockBlock, + } + if err := k.OutgoingPendingSwitches.Set(ctx, outK, entry); err != nil { + return err + } + inK := collections.Join(newReporter.Bytes(), selectorAddr.Bytes()) + if err := k.IncomingPendingSwitchIdx.Set(ctx, inK, prevReporter.Bytes()); err != nil { + return err + } + + if err := k.mergeReporterPendingSwitchHeadOutgoingAdd(ctx, prevReporter.Bytes(), unlockBlock); err != nil { + return err + } + if err := k.mergeReporterPendingSwitchHeadIncomingAdd(ctx, newReporter.Bytes(), unlockBlock); err != nil { + return err + } + + selection.SwitchOutLockedUntilBlock = unlockBlock + if err := k.Selectors.Set(ctx, selectorAddr.Bytes(), *selection); err != nil { + return err + } + if wasReplace { + return k.FlagStakeRecalc(ctx, sdk.AccAddress(existing.ToReporter)) + } + return nil +} + +func (k Keeper) reporterPendingSwitchHeadOrZero(ctx context.Context, rep []byte) (types.ReporterPendingSwitchHead, error) { + h, err := k.ReporterPendingSwitchHeads.Get(ctx, rep) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return types.ReporterPendingSwitchHead{}, nil + } + return types.ReporterPendingSwitchHead{}, err + } + return h, nil +} + +func (k Keeper) mergeReporterPendingSwitchHeadOutgoingAdd(ctx context.Context, rep []byte, unlock uint64) error { + head, err := k.reporterPendingSwitchHeadOrZero(ctx, rep) + if err != nil { + return err + } + head.OutgoingCount++ + if head.OutgoingCount == 1 || unlock < head.OutgoingMinUnlock { + head.OutgoingMinUnlock = unlock + } + return k.ReporterPendingSwitchHeads.Set(ctx, rep, head) +} + +func (k Keeper) mergeReporterPendingSwitchHeadIncomingAdd(ctx context.Context, rep []byte, unlock uint64) error { + head, err := k.reporterPendingSwitchHeadOrZero(ctx, rep) + if err != nil { + return err + } + head.IncomingCount++ + if head.IncomingCount == 1 || unlock < head.IncomingMinUnlock { + head.IncomingMinUnlock = unlock + } + return k.ReporterPendingSwitchHeads.Set(ctx, rep, head) +} + +// hasOutgoingPendingSwitch returns true if selector has a scheduled switch away +// from repAddr (stake must not count toward that reporter). +func (k Keeper) hasOutgoingPendingSwitch(ctx context.Context, repAddr, selectorAddr []byte) (bool, error) { + return k.OutgoingPendingSwitches.Has(ctx, collections.Join(repAddr, selectorAddr)) +} + +// pendingSwitchToReporter returns (true, toAddr) if there is a pending switch +// from prevReporter for this selector. +func (k Keeper) pendingSwitchToReporter(ctx context.Context, prevReporter, selectorAddr sdk.AccAddress) (bool, []byte, error) { + outK := collections.Join(prevReporter.Bytes(), selectorAddr.Bytes()) + e, err := k.OutgoingPendingSwitches.Get(ctx, outK) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return false, nil, nil + } + return false, nil, err + } + return true, e.ToReporter, nil +} diff --git a/x/reporter/keeper/query.go b/x/reporter/keeper/query.go index a5f58d62d..7a6d58a27 100644 --- a/x/reporter/keeper/query.go +++ b/x/reporter/keeper/query.go @@ -41,7 +41,7 @@ func (k Querier) Reporters(ctx context.Context, req *types.QueryReportersRequest if err != nil { return err } - stake, _, _, _, err := k.GetReporterStake(ctx, sdk.AccAddress(repAddr)) + stake, _, _, _, err := k.GetReporterStakeView(ctx, sdk.AccAddress(repAddr)) if err != nil { stake = math.ZeroInt() } @@ -223,6 +223,7 @@ func (k Querier) SelectionsTo(ctx context.Context, req *types.QuerySelectionsToR DelegationsCount: delegationCount, DelegationsTotal: totalTokens, IndividualDelegations: individualDelegations, + DisputeLockedUntil: selection.DisputeLockedUntil, } selections = append(selections, formattedSelection) } @@ -278,7 +279,7 @@ func (k Querier) JailedReporters(ctx context.Context, req *types.QueryJailedRepo if !reporterMeta.Jailed { return false, nil } - stake, _, _, _, err := k.GetReporterStake(ctx, sdk.AccAddress(repAddr)) + stake, _, _, _, err := k.GetReporterStakeView(ctx, sdk.AccAddress(repAddr)) if err != nil { stake = math.ZeroInt() } @@ -309,7 +310,7 @@ func (k Querier) Reporter(ctx context.Context, req *types.QueryReporterRequest) return nil, status.Error(codes.NotFound, "reporter not found") } - stake, _, _, _, err := k.GetReporterStake(ctx, repAddr) + stake, _, _, _, err := k.GetReporterStakeView(ctx, repAddr) if err != nil { stake = math.ZeroInt() } diff --git a/x/reporter/keeper/reporter.go b/x/reporter/keeper/reporter.go index 13e65ba0f..9792c2994 100644 --- a/x/reporter/keeper/reporter.go +++ b/x/reporter/keeper/reporter.go @@ -51,7 +51,12 @@ func (k Keeper) HasMin(ctx context.Context, addr sdk.AccAddress, minRequired mat // the token origins for each selector which is needed during a dispute for slashing/returning tokens to appropriate parties. // It also tracks period data for reward distribution - when delegation state changes, // the previous period is queued for distribution. +// Ready pending reporter switches involving this reporter are finalized first (same entry path as MsgSubmitValue). func (k Keeper) ReporterStake(ctx context.Context, repAddr sdk.AccAddress, queryId []byte) (math.Int, error) { + if err := k.applyReadyPendingSwitchesForReporter(ctx, repAddr); err != nil { + return math.Int{}, err + } + needsRecalc, err := k.needsStakeRecalc(ctx, repAddr) if err != nil { return math.Int{}, err @@ -194,15 +199,56 @@ func (k Keeper) GetNumOfSelectors(ctx context.Context, repAddr sdk.AccAddress) ( return len(keys), nil } +// CountSelectorsDelegatingToReporterExcludingSelf counts selector accounts whose +// Selection.reporter is repAddr, excluding repAddr itself (the self-reporter row). +func (k Keeper) CountSelectorsDelegatingToReporterExcludingSelf(ctx context.Context, repAddr sdk.AccAddress) (int, error) { + iter, err := k.Selectors.Indexes.Reporter.MatchExact(ctx, repAddr.Bytes()) + if err != nil { + return 0, err + } + keys, err := iter.PrimaryKeys() + if err != nil { + return 0, err + } + n := 0 + for _, selAddr := range keys { + if !bytes.Equal(selAddr, repAddr.Bytes()) { + n++ + } + } + return n, nil +} + +// GetSelector returns the stored selection row without mutating state (safe for queries). func (k Keeper) GetSelector(ctx context.Context, selectorAddr sdk.AccAddress) (types.Selection, error) { - return k.Selectors.Get(ctx, selectorAddr) + return k.Selectors.Get(ctx, selectorAddr.Bytes()) } -// GetReporterStake counts the total amount of BONDED tokens for a given reporter's selectors -// at the time of reporting and returns the total amount plus stores -// the token origins for each selector which is needed during a dispute for slashing/returning tokens to appropriate parties. -// Also returns aggregated selector shares and a hash of the delegation state for reward distribution. +// GetSelectorForStake returns the selection row and clears expired dispute-jail before +// stake counting or dispute voting (mutates state when the sentence has ended). +func (k Keeper) GetSelectorForStake(ctx context.Context, selectorAddr sdk.AccAddress) (types.Selection, error) { + sel, err := k.Selectors.Get(ctx, selectorAddr.Bytes()) + if err != nil { + return types.Selection{}, err + } + if err := k.lazyClearSelectorLocksIfExpired(ctx, selectorAddr, &sel); err != nil { + return types.Selection{}, err + } + return sel, nil +} + +// GetReporterStake counts bonded selector stake for reporting paths. It may lazy-unjail +// expired selector rows and update RecalcAtTime when locks are still active. func (k Keeper) GetReporterStake(ctx context.Context, repAddr sdk.AccAddress) (math.Int, []*types.TokenOriginInfo, []*types.SelectorShare, []byte, error) { + return k.getReporterStake(ctx, repAddr, true) +} + +// GetReporterStakeView is the read-only stake snapshot used by gRPC queries. +func (k Keeper) GetReporterStakeView(ctx context.Context, repAddr sdk.AccAddress) (math.Int, []*types.TokenOriginInfo, []*types.SelectorShare, []byte, error) { + return k.getReporterStake(ctx, repAddr, false) +} + +func (k Keeper) getReporterStake(ctx context.Context, repAddr sdk.AccAddress, mutate bool) (math.Int, []*types.TokenOriginInfo, []*types.SelectorShare, []byte, error) { reporter, err := k.Reporters.Get(ctx, repAddr.Bytes()) if err != nil { return math.Int{}, nil, nil, nil, err @@ -232,16 +278,37 @@ func (k Keeper) GetReporterStake(ctx context.Context, repAddr sdk.AccAddress) (m if err != nil { return math.Int{}, nil, nil, nil, err } - // get delegator count - selector, err := k.Selectors.Get(ctx, selectorAddr) + var selector types.Selection + if mutate { + selector, err = k.GetSelectorForStake(ctx, sdk.AccAddress(selectorAddr)) + } else { + selector, err = k.GetSelector(ctx, sdk.AccAddress(selectorAddr)) + } if err != nil { return math.Int{}, nil, nil, nil, err } - // skip selectors that are locked out for switching reporters - if selector.LockedUntilTime.After(sdk.UnwrapSDKContext(ctx).BlockTime()) { - lockUnix := selector.LockedUntilTime.Unix() - if earliestFutureLock == 0 || lockUnix < earliestFutureLock { - earliestFutureLock = lockUnix + // Skip selectors with a pending switch away from this reporter: their + // stake no longer counts toward the outgoing reporter until ReporterStake + // finalizes the handoff after unlock height. + hasPending, err := k.hasOutgoingPendingSwitch(ctx, repAddr.Bytes(), selectorAddr) + if err != nil { + return math.Int{}, nil, nil, nil, err + } + if hasPending && bytes.Equal(selector.Reporter, repAddr.Bytes()) { + continue + } + // skip dispute-locked selectors (locked_until_time and/or jailed) + now := sdk.UnwrapSDKContext(ctx).BlockTime() + if selectorStakeLocked(selector, now) { + lockUntil := selector.LockedUntilTime + if selector.DisputeLockedUntil.After(lockUntil) { + lockUntil = selector.DisputeLockedUntil + } + if lockUntil.After(now) { + lockUnix := lockUntil.Unix() + if earliestFutureLock == 0 || lockUnix < earliestFutureLock { + earliestFutureLock = lockUnix + } } continue } @@ -312,15 +379,16 @@ func (k Keeper) GetReporterStake(ctx context.Context, repAddr sdk.AccAddress) (m hasher.Write(selectorTotal.BigInt().Bytes()) } } - // Update RecalcAtTime: if there are still-locked selectors, set the earliest expiry - // so needsStakeRecalc triggers when it expires. If none are locked, clean up. - if earliestFutureLock == 0 { - err = k.RecalcAtTime.Remove(ctx, repAddr.Bytes()) - } else { - err = k.RecalcAtTime.Set(ctx, repAddr.Bytes(), earliestFutureLock) - } - if err != nil { - return math.Int{}, nil, nil, nil, err + // Update RecalcAtTime on write paths only (queries must not mutate store). + if mutate { + if earliestFutureLock == 0 { + err = k.RecalcAtTime.Remove(ctx, repAddr.Bytes()) + } else { + err = k.RecalcAtTime.Set(ctx, repAddr.Bytes(), earliestFutureLock) + } + if err != nil { + return math.Int{}, nil, nil, nil, err + } } // Finalize hash with total diff --git a/x/reporter/keeper/selection_lock.go b/x/reporter/keeper/selection_lock.go new file mode 100644 index 000000000..246197fea --- /dev/null +++ b/x/reporter/keeper/selection_lock.go @@ -0,0 +1,11 @@ +package keeper + +import ( + "time" + + "github.com/tellor-io/layer/x/reporter/types" +) + +func selectorStakeLocked(sel types.Selection, now time.Time) bool { + return types.SelectorStakeLocked(sel, now) +} diff --git a/x/reporter/mocks/OracleKeeper.go b/x/reporter/mocks/OracleKeeper.go index 6d52a336e..a8beaa916 100644 --- a/x/reporter/mocks/OracleKeeper.go +++ b/x/reporter/mocks/OracleKeeper.go @@ -62,6 +62,30 @@ func (_m *OracleKeeper) GetLastReportedAtTimestamp(ctx context.Context, reporter return r0, r1 } +// GetMaxOpenCommitmentForReporter provides a mock function with given fields: ctx, reporter +func (_m *OracleKeeper) GetMaxOpenCommitmentForReporter(ctx context.Context, reporter []byte) (uint64, error) { + ret := _m.Called(ctx, reporter) + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte) (uint64, error)); ok { + return rf(ctx, reporter) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte) uint64); ok { + r0 = rf(ctx, reporter) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, reporter) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type mockConstructorTestingTNewOracleKeeper interface { mock.TestingT Cleanup(func()) diff --git a/x/reporter/module/autocli.go b/x/reporter/module/autocli.go index c0002a9ad..0a1bfd370 100644 --- a/x/reporter/module/autocli.go +++ b/x/reporter/module/autocli.go @@ -117,9 +117,9 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions { }, { RpcMethod: "UnjailReporter", - Use: "unjail-reporter", + Use: "unjail-reporter [reporter-address]", Short: "Execute the UnjailReporter RPC method", - PositionalArgs: []*autocliv1.PositionalArgDescriptor{}, + PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "reporter_address"}}, }, { RpcMethod: "WithdrawTip", diff --git a/x/reporter/module/module.go b/x/reporter/module/module.go index c4545c661..6424d5111 100644 --- a/x/reporter/module/module.go +++ b/x/reporter/module/module.go @@ -31,9 +31,8 @@ var ( _ module.HasGenesis = (*AppModule)(nil) _ module.HasConsensusVersion = (*AppModule)(nil) - _ appmodule.AppModule = (*AppModule)(nil) - _ appmodule.HasBeginBlocker = (*AppModule)(nil) - _ appmodule.HasEndBlocker = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) + _ appmodule.HasEndBlocker = (*AppModule)(nil) ) // ---------------------------------------------------------------------------- @@ -148,12 +147,6 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // To avoid wrong/empty versions, the initial version should be set to 1. func (AppModule) ConsensusVersion() uint64 { return 1 } -// BeginBlock contains the logic that is automatically triggered at the beginning of each block. -// The begin block implementation is optional. -func (am AppModule) BeginBlock(_ context.Context) error { - return nil -} - // EndBlock contains the logic that is automatically triggered at the end of each block. // The end block implementation is optional. func (am AppModule) EndBlock(ctx context.Context) error { diff --git a/x/reporter/types/errors.go b/x/reporter/types/errors.go index cc4706dd7..bfabf3fbe 100644 --- a/x/reporter/types/errors.go +++ b/x/reporter/types/errors.go @@ -22,7 +22,8 @@ var ( ErrReporterDoesNotExist = sdkerrors.Register(ModuleName, 1111, "reporter does not exist") ErrReporterJailed = sdkerrors.Register(ModuleName, 1112, "reporter jailed") ErrReporterNotJailed = sdkerrors.Register(ModuleName, 1113, "reporter not jailed") + ErrThirdPartyUnjailTooEarly = sdkerrors.Register(ModuleName, 1116, "third-party unjail not yet allowed") ErrNoUnbondingDelegationEntries = sdkerrors.Register(ModuleName, 1114, "no unbonding delegation entries") ErrExceedsMaxDelegations = sdkerrors.Register(ModuleName, 1115, "exceeds max number of delegations") - ErrExceedsMaxStakeShare = sdkerrors.Register(ModuleName, 1116, "delegator bonded stake exceeds 30% of total bonded stake") + ErrExceedsMaxStakeShare = sdkerrors.Register(ModuleName, 1117, "delegator bonded stake exceeds 30% of total bonded stake") ) diff --git a/x/reporter/types/expected_keepers.go b/x/reporter/types/expected_keepers.go index 824ae90f7..f4692ee1e 100644 --- a/x/reporter/types/expected_keepers.go +++ b/x/reporter/types/expected_keepers.go @@ -80,4 +80,5 @@ type RegistryKeeper interface { type OracleKeeper interface { GetLastReportedAtTimestamp(ctx context.Context, reporter []byte) (uint64, error) GetBlockHeightFromTimestamp(ctx context.Context, timestamp time.Time) (uint64, error) + GetMaxOpenCommitmentForReporter(ctx context.Context, reporter []byte) (uint64, error) } diff --git a/x/reporter/types/genesis_test.go b/x/reporter/types/genesis_test.go index 6674a229c..583f49983 100644 --- a/x/reporter/types/genesis_test.go +++ b/x/reporter/types/genesis_test.go @@ -19,10 +19,9 @@ func TestGenesisState_Validate(t *testing.T) { valid: true, }, { - desc: "valid genesis state", + desc: "valid genesis state", genState: &types.GenesisState{ - - // this line is used by starport scaffolding # types/genesis/validField + Params: types.DefaultParams(), }, valid: true, }, diff --git a/x/reporter/types/keys.go b/x/reporter/types/keys.go index 4b0bba328..54bd3583d 100644 --- a/x/reporter/types/keys.go +++ b/x/reporter/types/keys.go @@ -38,4 +38,8 @@ var ( RecalcAtTimePrefix = collections.NewPrefix(27) ReportByBlockPrefix = collections.NewPrefix(28) ReportByBlockNumberIndexPrefix = collections.NewPrefix(29) + // Deferred reporter switch: outgoing (from,selector) rows, incoming index, per-reporter heads. + OutgoingPendingSwitchPrefix = collections.NewPrefix(32) + IncomingPendingSwitchIdxPrefix = collections.NewPrefix(33) + ReporterPendingSwitchHeadPrefix = collections.NewPrefix(34) ) diff --git a/x/reporter/types/params.go b/x/reporter/types/params.go index f27280b2f..78734d6ac 100644 --- a/x/reporter/types/params.go +++ b/x/reporter/types/params.go @@ -13,13 +13,15 @@ var _ paramtypes.ParamSet = (*Params)(nil) var ( KeyMinCommissionRate = []byte("MinCommissionRate") // TODO: Determine the default value - DefaultMinCommissionRate = math.LegacyZeroDec() - KeyMinLoya = []byte("MinLoya") - DefaultMinLoya = math.NewIntWithDecimal(1, 6) - KeyMaxSelectors = []byte("MaxSelectors") - DefaultMaxSelectors = uint64(100) - KeyMaxNumOfDelegations = []byte("MaxNumOfDelegations") - DefaultMaxNumOfDelegations = uint64(10) + DefaultMinCommissionRate = math.LegacyZeroDec() + KeyMinLoya = []byte("MinLoya") + DefaultMinLoya = math.NewIntWithDecimal(1, 6) + KeyMaxSelectors = []byte("MaxSelectors") + DefaultMaxSelectors = uint64(100) + KeyMaxNumOfDelegations = []byte("MaxNumOfDelegations") + DefaultMaxNumOfDelegations = uint64(10) + KeyMaxPendingSwitchesPerReporter = []byte("MaxPendingSwitchesPerReporter") + DefaultMaxPendingSwitchesPerReporter = uint64(10) ) // ParamKeyTable the param key table for launch module @@ -33,12 +35,14 @@ func NewParams( minLoya math.Int, maxSelectors uint64, maxNumOfDelegations uint64, + maxPendingSwitchesPerReporter uint64, ) Params { return Params{ - MinCommissionRate: minCommissionRate, - MinLoya: minLoya, - MaxSelectors: maxSelectors, - MaxNumOfDelegations: maxNumOfDelegations, + MinCommissionRate: minCommissionRate, + MinLoya: minLoya, + MaxSelectors: maxSelectors, + MaxNumOfDelegations: maxNumOfDelegations, + MaxPendingSwitchesPerReporter: maxPendingSwitchesPerReporter, } } @@ -49,6 +53,7 @@ func DefaultParams() Params { DefaultMinLoya, DefaultMaxSelectors, DefaultMaxNumOfDelegations, + DefaultMaxPendingSwitchesPerReporter, ) } @@ -59,6 +64,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyMinLoya, &p.MinLoya, validateMinLoya), paramtypes.NewParamSetPair(KeyMaxSelectors, &p.MaxSelectors, validateMaxSelectors), paramtypes.NewParamSetPair(KeyMaxNumOfDelegations, &p.MaxNumOfDelegations, validateMaxNumOfDelegations), + paramtypes.NewParamSetPair(KeyMaxPendingSwitchesPerReporter, &p.MaxPendingSwitchesPerReporter, validateMaxPendingSwitchesPerReporter), } } @@ -76,6 +82,9 @@ func (p Params) Validate() error { if err := validateMaxNumOfDelegations(p.MaxNumOfDelegations); err != nil { return err } + if err := validateMaxPendingSwitchesPerReporter(p.MaxPendingSwitchesPerReporter); err != nil { + return err + } return nil } @@ -116,3 +125,14 @@ func validateMaxNumOfDelegations(v interface{}) error { return nil } + +func validateMaxPendingSwitchesPerReporter(v interface{}) error { + n, ok := v.(uint64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", v) + } + if n == 0 { + return fmt.Errorf("max pending switches per reporter must be positive") + } + return nil +} diff --git a/x/reporter/types/params.pb.go b/x/reporter/types/params.pb.go index 061d1ef64..0ff4fdf0f 100644 --- a/x/reporter/types/params.pb.go +++ b/x/reporter/types/params.pb.go @@ -40,6 +40,9 @@ type Params struct { MaxSelectors uint64 `protobuf:"varint,3,opt,name=max_selectors,json=maxSelectors,proto3" json:"max_selectors,omitempty"` // max number of validators a user can delegate too MaxNumOfDelegations uint64 `protobuf:"varint,4,opt,name=max_num_of_delegations,json=maxNumOfDelegations,proto3" json:"max_num_of_delegations,omitempty"` + // max pending reporter switches involving a reporter as outgoing or incoming + // (each side capped separately when scheduling a switch). + MaxPendingSwitchesPerReporter uint64 `protobuf:"varint,5,opt,name=max_pending_switches_per_reporter,json=maxPendingSwitchesPerReporter,proto3" json:"max_pending_switches_per_reporter,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -89,6 +92,13 @@ func (m *Params) GetMaxNumOfDelegations() uint64 { return 0 } +func (m *Params) GetMaxPendingSwitchesPerReporter() uint64 { + if m != nil { + return m.MaxPendingSwitchesPerReporter + } + return 0 +} + type StakeTracker struct { Expiration *time.Time `protobuf:"bytes,1,opt,name=expiration,proto3,stdtime" json:"expiration,omitempty"` Amount cosmossdk_io_math.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=cosmossdk.io/math.Int" json:"amount" yaml:"amount"` @@ -142,38 +152,41 @@ func init() { func init() { proto.RegisterFile("layer/reporter/params.proto", fileDescriptor_2b46dabd827272cb) } var fileDescriptor_2b46dabd827272cb = []byte{ - // 487 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x51, 0xbf, 0x6f, 0xd3, 0x40, - 0x14, 0x8e, 0x4b, 0x14, 0xe0, 0x68, 0x41, 0x75, 0xf9, 0x11, 0x52, 0xc9, 0x8e, 0xc2, 0x52, 0x81, - 0x6a, 0x4b, 0x74, 0xab, 0x10, 0x42, 0x21, 0x0c, 0x95, 0x2a, 0x8a, 0xd2, 0x2c, 0xb0, 0x58, 0x17, - 0xf7, 0xe2, 0x9e, 0xe2, 0x77, 0xcf, 0xba, 0x3b, 0x4b, 0xf6, 0xc4, 0xce, 0xd4, 0x3f, 0x81, 0x89, - 0x99, 0x81, 0x3f, 0xa2, 0x63, 0xc5, 0x84, 0x18, 0x42, 0x95, 0x0c, 0x30, 0xf7, 0x2f, 0x40, 0xb9, - 0x73, 0x48, 0x45, 0x91, 0x58, 0x2c, 0xbf, 0xf7, 0xbe, 0xef, 0x7d, 0xf7, 0xbe, 0x8f, 0x6c, 0xa6, - 0xb4, 0x64, 0x32, 0x94, 0x2c, 0x43, 0xa9, 0x99, 0x0c, 0x33, 0x2a, 0x29, 0xa8, 0x20, 0x93, 0xa8, - 0xd1, 0xbd, 0x6d, 0x86, 0xc1, 0x62, 0xd8, 0x5a, 0xa7, 0xc0, 0x05, 0x86, 0xe6, 0x6b, 0x21, 0xad, - 0x87, 0x31, 0x2a, 0x40, 0x15, 0x99, 0x2a, 0xb4, 0x45, 0x35, 0xba, 0x9b, 0x60, 0x82, 0xb6, 0x3f, - 0xff, 0xab, 0xba, 0x7e, 0x82, 0x98, 0xa4, 0x2c, 0x34, 0xd5, 0x30, 0x1f, 0x85, 0x9a, 0x03, 0x53, - 0x9a, 0x42, 0x66, 0x01, 0x9d, 0xf3, 0x15, 0xd2, 0x78, 0x63, 0x5e, 0xe1, 0xbe, 0x27, 0x1b, 0xc0, - 0x45, 0x14, 0x23, 0x00, 0x57, 0x8a, 0xa3, 0x88, 0x24, 0xd5, 0xac, 0xe9, 0xb4, 0x9d, 0xad, 0x9b, - 0xdd, 0x83, 0xd3, 0x89, 0x5f, 0xfb, 0x3e, 0xf1, 0x37, 0xad, 0xa8, 0x3a, 0x1a, 0x07, 0x1c, 0x43, - 0xa0, 0xfa, 0x38, 0xd8, 0x67, 0x09, 0x8d, 0xcb, 0x1e, 0x8b, 0x2f, 0x26, 0x7e, 0xab, 0xa4, 0x90, - 0xee, 0x76, 0xfe, 0xb1, 0xa7, 0xf3, 0xf5, 0xcb, 0x36, 0xa9, 0x5e, 0xdc, 0x63, 0x71, 0x7f, 0x1d, - 0xb8, 0x78, 0xf9, 0x07, 0xd2, 0xa7, 0x9a, 0xb9, 0x6f, 0xc9, 0x8d, 0x39, 0x31, 0xc5, 0x92, 0x36, - 0x57, 0x8c, 0xea, 0xf3, 0x4a, 0xf5, 0xde, 0x55, 0xd5, 0x3d, 0xa1, 0x2f, 0x26, 0xfe, 0x9d, 0xa5, - 0xde, 0x9c, 0x76, 0x59, 0x64, 0x4f, 0xe8, 0xfe, 0x75, 0xe0, 0x62, 0x1f, 0x4b, 0xea, 0x3e, 0x22, - 0x6b, 0x40, 0x8b, 0x48, 0xb1, 0x94, 0xc5, 0x1a, 0xa5, 0x6a, 0x5e, 0x6b, 0x3b, 0x5b, 0xf5, 0xfe, - 0x2a, 0xd0, 0xe2, 0x70, 0xd1, 0x73, 0x77, 0xc8, 0xfd, 0x39, 0x48, 0xe4, 0x10, 0xe1, 0x28, 0x3a, - 0x62, 0x29, 0x4b, 0xa8, 0xe6, 0x28, 0x54, 0xb3, 0x6e, 0xd0, 0x1b, 0x40, 0x8b, 0xd7, 0x39, 0x1c, - 0x8c, 0x7a, 0xcb, 0xd1, 0x6e, 0xfb, 0xd7, 0x47, 0xdf, 0xf9, 0xf0, 0xf3, 0xf3, 0xe3, 0x07, 0x36, - 0xdb, 0x62, 0x99, 0xae, 0xf5, 0xb5, 0xf3, 0xc9, 0x21, 0xab, 0x87, 0x9a, 0x8e, 0xd9, 0x40, 0xd2, - 0x78, 0xcc, 0xa4, 0xfb, 0x82, 0x10, 0x56, 0x64, 0x5c, 0x9a, 0x0d, 0xc6, 0xdf, 0x5b, 0x4f, 0x5b, - 0x81, 0x4d, 0x2a, 0x58, 0x24, 0x15, 0x0c, 0x16, 0x49, 0x75, 0xeb, 0x27, 0x3f, 0x7c, 0xa7, 0x7f, - 0x89, 0xe3, 0x0e, 0x48, 0x83, 0x02, 0xe6, 0x42, 0x57, 0x3e, 0x3d, 0xfb, 0x9f, 0x4f, 0x6b, 0xd6, - 0x27, 0x4b, 0xfa, 0xdb, 0xa5, 0x6a, 0x57, 0xf7, 0xd5, 0xe9, 0xd4, 0x73, 0xce, 0xa6, 0x9e, 0x73, - 0x3e, 0xf5, 0x9c, 0x93, 0x99, 0x57, 0x3b, 0x9b, 0x79, 0xb5, 0x6f, 0x33, 0xaf, 0xf6, 0xee, 0x49, - 0xc2, 0xf5, 0x71, 0x3e, 0x0c, 0x62, 0x84, 0x50, 0xb3, 0x34, 0x45, 0xb9, 0xcd, 0x31, 0xbc, 0x72, - 0xb0, 0x2e, 0x33, 0xa6, 0x86, 0x0d, 0x73, 0xc2, 0xce, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa1, - 0x32, 0x71, 0x7b, 0xed, 0x02, 0x00, 0x00, + // 529 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xbf, 0x6f, 0xd3, 0x40, + 0x14, 0x8e, 0x69, 0x29, 0x70, 0xb4, 0xa0, 0xba, 0xfc, 0x08, 0xa9, 0xb0, 0x4b, 0x58, 0x2a, 0x50, + 0x6d, 0x89, 0x6e, 0x15, 0x42, 0x28, 0x04, 0x89, 0x4a, 0x15, 0x8d, 0x9c, 0x2c, 0xb0, 0x58, 0x17, + 0xe7, 0xc5, 0x39, 0xc5, 0x77, 0xcf, 0xba, 0xbb, 0x08, 0x7b, 0x62, 0x67, 0xea, 0x9f, 0xc0, 0xc4, + 0xc0, 0xc4, 0xc0, 0x1f, 0xd1, 0xb1, 0x62, 0x42, 0x0c, 0x01, 0x25, 0x03, 0xcc, 0xfd, 0x0b, 0x50, + 0x7c, 0x0e, 0xa9, 0x28, 0x12, 0x8b, 0xe5, 0xf7, 0xde, 0xf7, 0xdd, 0xf7, 0xee, 0xfb, 0x8e, 0x6c, + 0x26, 0x34, 0x07, 0xe9, 0x4b, 0x48, 0x51, 0x6a, 0x90, 0x7e, 0x4a, 0x25, 0xe5, 0xca, 0x4b, 0x25, + 0x6a, 0xb4, 0xaf, 0x15, 0x43, 0x6f, 0x3e, 0xac, 0xad, 0x53, 0xce, 0x04, 0xfa, 0xc5, 0xd7, 0x40, + 0x6a, 0x77, 0x22, 0x54, 0x1c, 0x55, 0x58, 0x54, 0xbe, 0x29, 0xca, 0xd1, 0x8d, 0x18, 0x63, 0x34, + 0xfd, 0xd9, 0x5f, 0xd9, 0x75, 0x63, 0xc4, 0x38, 0x01, 0xbf, 0xa8, 0xba, 0xa3, 0xbe, 0xaf, 0x19, + 0x07, 0xa5, 0x29, 0x4f, 0x0d, 0xa0, 0xfe, 0x71, 0x89, 0xac, 0xb4, 0x8a, 0x2d, 0xec, 0xb7, 0x64, + 0x83, 0x33, 0x11, 0x46, 0xc8, 0x39, 0x53, 0x8a, 0xa1, 0x08, 0x25, 0xd5, 0x50, 0xb5, 0xb6, 0xac, + 0xed, 0x2b, 0x8d, 0xc3, 0xe3, 0xb1, 0x5b, 0xf9, 0x36, 0x76, 0x37, 0x8d, 0xa8, 0xea, 0x0d, 0x3d, + 0x86, 0x3e, 0xa7, 0x7a, 0xe0, 0x1d, 0x40, 0x4c, 0xa3, 0xbc, 0x09, 0xd1, 0xe9, 0xd8, 0xad, 0xe5, + 0x94, 0x27, 0x7b, 0xf5, 0x7f, 0x9c, 0x53, 0xff, 0xf2, 0x79, 0x87, 0x94, 0x1b, 0x37, 0x21, 0x0a, + 0xd6, 0x39, 0x13, 0xcf, 0xfe, 0x40, 0x02, 0xaa, 0xc1, 0x7e, 0x45, 0x2e, 0xcf, 0x88, 0x09, 0xe6, + 0xb4, 0x7a, 0xa1, 0x50, 0x7d, 0x52, 0xaa, 0xde, 0x3c, 0xaf, 0xba, 0x2f, 0xf4, 0xe9, 0xd8, 0xbd, + 0xbe, 0xd0, 0x9b, 0xd1, 0xce, 0x8a, 0xec, 0x0b, 0x1d, 0x5c, 0xe2, 0x4c, 0x1c, 0x60, 0x4e, 0xed, + 0xfb, 0x64, 0x8d, 0xd3, 0x2c, 0x54, 0x90, 0x40, 0xa4, 0x51, 0xaa, 0xea, 0xd2, 0x96, 0xb5, 0xbd, + 0x1c, 0xac, 0x72, 0x9a, 0xb5, 0xe7, 0x3d, 0x7b, 0x97, 0xdc, 0x9a, 0x81, 0xc4, 0x88, 0x87, 0xd8, + 0x0f, 0x7b, 0x90, 0x40, 0x4c, 0x35, 0x43, 0xa1, 0xaa, 0xcb, 0x05, 0x7a, 0x83, 0xd3, 0xec, 0xe5, + 0x88, 0x1f, 0xf6, 0x9b, 0x8b, 0x91, 0xfd, 0x82, 0xdc, 0x9b, 0x91, 0x52, 0x10, 0x3d, 0x26, 0xe2, + 0x50, 0xbd, 0x61, 0x3a, 0x1a, 0x80, 0x0a, 0x53, 0x90, 0xe1, 0x3c, 0xca, 0xea, 0xc5, 0x82, 0x7f, + 0x97, 0xd3, 0xac, 0x65, 0x70, 0xed, 0x12, 0xd6, 0x02, 0x19, 0x94, 0xa0, 0xbd, 0xad, 0x5f, 0xef, + 0x5d, 0xeb, 0xdd, 0xcf, 0x4f, 0x0f, 0x6e, 0x9b, 0x57, 0x92, 0x2d, 0xde, 0x89, 0x49, 0xa8, 0xfe, + 0xc1, 0x22, 0xab, 0x6d, 0x4d, 0x87, 0xd0, 0x91, 0x34, 0x1a, 0x82, 0xb4, 0x9f, 0x12, 0x02, 0x59, + 0xca, 0x64, 0xb1, 0x4b, 0x91, 0xd4, 0xd5, 0x47, 0x35, 0xcf, 0x64, 0xee, 0xcd, 0x33, 0xf7, 0x3a, + 0xf3, 0xcc, 0x1b, 0xcb, 0x47, 0xdf, 0x5d, 0x2b, 0x38, 0xc3, 0xb1, 0x3b, 0x64, 0x85, 0x72, 0x1c, + 0x09, 0x5d, 0x3a, 0xfe, 0xf8, 0x7f, 0x8e, 0xaf, 0x19, 0xc7, 0x0d, 0xe9, 0x6f, 0xbf, 0xcb, 0xb3, + 0x1a, 0xcf, 0x8f, 0x27, 0x8e, 0x75, 0x32, 0x71, 0xac, 0x1f, 0x13, 0xc7, 0x3a, 0x9a, 0x3a, 0x95, + 0x93, 0xa9, 0x53, 0xf9, 0x3a, 0x75, 0x2a, 0xaf, 0x1f, 0xc6, 0x4c, 0x0f, 0x46, 0x5d, 0x2f, 0x42, + 0xee, 0x6b, 0x48, 0x12, 0x94, 0x3b, 0x0c, 0xfd, 0x73, 0x17, 0xd6, 0x79, 0x0a, 0xaa, 0xbb, 0x52, + 0x5c, 0x61, 0xf7, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x36, 0xfd, 0x6b, 0xce, 0x37, 0x03, 0x00, + 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -207,6 +220,9 @@ func (this *Params) Equal(that interface{}) bool { if this.MaxNumOfDelegations != that1.MaxNumOfDelegations { return false } + if this.MaxPendingSwitchesPerReporter != that1.MaxPendingSwitchesPerReporter { + return false + } return true } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -229,6 +245,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxPendingSwitchesPerReporter != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxPendingSwitchesPerReporter)) + i-- + dAtA[i] = 0x28 + } if m.MaxNumOfDelegations != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MaxNumOfDelegations)) i-- @@ -332,6 +353,9 @@ func (m *Params) Size() (n int) { if m.MaxNumOfDelegations != 0 { n += 1 + sovParams(uint64(m.MaxNumOfDelegations)) } + if m.MaxPendingSwitchesPerReporter != 0 { + n += 1 + sovParams(uint64(m.MaxPendingSwitchesPerReporter)) + } return n } @@ -491,6 +515,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxPendingSwitchesPerReporter", wireType) + } + m.MaxPendingSwitchesPerReporter = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxPendingSwitchesPerReporter |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/reporter/types/params_test.go b/x/reporter/types/params_test.go index b71f3a357..43e979b82 100644 --- a/x/reporter/types/params_test.go +++ b/x/reporter/types/params_test.go @@ -14,28 +14,29 @@ import ( func TestParams_NewParams(t *testing.T) { require := require.New(t) - params := NewParams(math.LegacyNewDec(5), math.NewInt(1), 100, 10) + params := NewParams(math.LegacyNewDec(5), math.NewInt(1), 100, 10, 10) require.NoError(params.Validate()) require.Equal(params.MinCommissionRate, math.LegacyNewDec(5)) require.Equal(params.MinLoya, math.NewInt(1)) require.Equal(params.MaxSelectors, uint64(100)) require.Equal(params.MaxNumOfDelegations, uint64(10)) + require.Equal(params.MaxPendingSwitchesPerReporter, uint64(10)) - params = NewParams(math.LegacyZeroDec(), math.NewInt(0), 0, 0) + params = NewParams(math.LegacyZeroDec(), math.NewInt(0), 0, 0, 1) require.NoError(params.Validate()) require.Equal(params.MinCommissionRate, math.LegacyZeroDec()) require.Equal(params.MinLoya, math.NewInt(0)) require.Equal(params.MaxSelectors, uint64(0)) require.Equal(params.MaxNumOfDelegations, uint64(0)) - params = NewParams(math.LegacyNewDec(100), math.NewInt(100), 100, 100) + params = NewParams(math.LegacyNewDec(100), math.NewInt(100), 100, 100, 10) require.NoError(params.Validate()) require.Equal(params.MinCommissionRate, math.LegacyNewDec(100)) require.Equal(params.MinLoya, math.NewInt(100)) require.Equal(params.MaxSelectors, uint64(100)) require.Equal(params.MaxNumOfDelegations, uint64(100)) - params = NewParams(math.LegacyNewDec(100), math.NewInt(1000), 1000, 1000) + params = NewParams(math.LegacyNewDec(100), math.NewInt(1000), 1000, 1000, 10) require.NoError(params.Validate()) require.Equal(params.MinCommissionRate, math.LegacyNewDec(100)) require.Equal(params.MinLoya, math.NewInt(1000)) @@ -52,6 +53,7 @@ func TestParams_DefaultParams(t *testing.T) { require.Equal(params.MinCommissionRate, DefaultMinCommissionRate) require.Equal(params.MaxSelectors, DefaultMaxSelectors) require.Equal(params.MaxNumOfDelegations, DefaultMaxNumOfDelegations) + require.Equal(params.MaxPendingSwitchesPerReporter, DefaultMaxPendingSwitchesPerReporter) } func TestParams_ParamSetPairs(t *testing.T) { @@ -65,6 +67,7 @@ func TestParams_ParamSetPairs(t *testing.T) { {Key: KeyMinLoya, Value: ¶ms.MinLoya, ValidatorFn: validateMinLoya}, {Key: KeyMaxSelectors, Value: ¶ms.MaxSelectors, ValidatorFn: validateMaxSelectors}, {Key: KeyMaxNumOfDelegations, Value: ¶ms.MaxNumOfDelegations, ValidatorFn: validateMaxNumOfDelegations}, + {Key: KeyMaxPendingSwitchesPerReporter, Value: ¶ms.MaxPendingSwitchesPerReporter, ValidatorFn: validateMaxPendingSwitchesPerReporter}, } for i := range expected { diff --git a/x/reporter/types/query.pb.go b/x/reporter/types/query.pb.go index 56b3672b3..8ffb0a543 100644 --- a/x/reporter/types/query.pb.go +++ b/x/reporter/types/query.pb.go @@ -1181,9 +1181,9 @@ type QueryClient interface { AllowedAmountExpiration(ctx context.Context, in *QueryAllowedAmountExpirationRequest, opts ...grpc.CallOption) (*QueryAllowedAmountExpirationResponse, error) // NumOfSelectorsByReporter queries the number of selectors by a reporter. NumOfSelectorsByReporter(ctx context.Context, in *QueryNumOfSelectorsByReporterRequest, opts ...grpc.CallOption) (*QueryNumOfSelectorsByReporterResponse, error) - // SpaceAvailableByReporter queries the space available in a reporter. + // SpaceAvailableByReporter queries the space available in a reporter. SpaceAvailableByReporter(ctx context.Context, in *QuerySpaceAvailableByReporterRequest, opts ...grpc.CallOption) (*QuerySpaceAvailableByReporterResponse, error) - // AvailableTips queries the tips available for withdrawal for a given selector. + // AvailableTips queries the tips available for withdrawal for a given selector. AvailableTips(ctx context.Context, in *QueryAvailableTipsRequest, opts ...grpc.CallOption) (*QueryAvailableTipsResponse, error) // SelectionsTo queries the selections for a given reporter. SelectionsTo(ctx context.Context, in *QuerySelectionsToRequest, opts ...grpc.CallOption) (*QuerySelectionsToResponse, error) @@ -1313,9 +1313,9 @@ type QueryServer interface { AllowedAmountExpiration(context.Context, *QueryAllowedAmountExpirationRequest) (*QueryAllowedAmountExpirationResponse, error) // NumOfSelectorsByReporter queries the number of selectors by a reporter. NumOfSelectorsByReporter(context.Context, *QueryNumOfSelectorsByReporterRequest) (*QueryNumOfSelectorsByReporterResponse, error) - // SpaceAvailableByReporter queries the space available in a reporter. + // SpaceAvailableByReporter queries the space available in a reporter. SpaceAvailableByReporter(context.Context, *QuerySpaceAvailableByReporterRequest) (*QuerySpaceAvailableByReporterResponse, error) - // AvailableTips queries the tips available for withdrawal for a given selector. + // AvailableTips queries the tips available for withdrawal for a given selector. AvailableTips(context.Context, *QueryAvailableTipsRequest) (*QueryAvailableTipsResponse, error) // SelectionsTo queries the selections for a given reporter. SelectionsTo(context.Context, *QuerySelectionsToRequest) (*QuerySelectionsToResponse, error) diff --git a/x/reporter/types/selection.pb.go b/x/reporter/types/selection.pb.go index 62792dc15..25fd4efdd 100644 --- a/x/reporter/types/selection.pb.go +++ b/x/reporter/types/selection.pb.go @@ -34,11 +34,17 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Selection struct { // reporter is the address of the reporter being delegated to Reporter []byte `protobuf:"bytes,1,opt,name=reporter,proto3" json:"reporter,omitempty"` - // locked_until_time is the time until which the tokens are locked before they - // can be used for reporting again + // locked_until_time — non-dispute / legacy stake exclusion only (e.g. pre-upgrade switch locks). LockedUntilTime time.Time `protobuf:"bytes,2,opt,name=locked_until_time,json=lockedUntilTime,proto3,stdtime" json:"locked_until_time"` // delegations_count is the number of delegations to the reporter DelegationsCount uint64 `protobuf:"varint,3,opt,name=delegations_count,json=delegationsCount,proto3" json:"delegations_count,omitempty"` + // switch_out_locked_until_block is the block height until which this selector + // cannot initiate another reporter switch while a handoff is incomplete: it + // stores the oracle snapshot (max open query expiration for the outgoing + // reporter at schedule time). Cleared when the pending switch is finalized. + SwitchOutLockedUntilBlock uint64 `protobuf:"varint,4,opt,name=switch_out_locked_until_block,json=switchOutLockedUntilBlock,proto3" json:"switch_out_locked_until_block,omitempty"` + // dispute_locked_until — dispute jail only; max on set; never copied to locked_until_time. + DisputeLockedUntil time.Time `protobuf:"bytes,5,opt,name=dispute_locked_until,json=disputeLockedUntil,proto3,stdtime" json:"dispute_locked_until"` } func (m *Selection) Reset() { *m = Selection{} } @@ -95,6 +101,143 @@ func (m *Selection) GetDelegationsCount() uint64 { return 0 } +func (m *Selection) GetSwitchOutLockedUntilBlock() uint64 { + if m != nil { + return m.SwitchOutLockedUntilBlock + } + return 0 +} + +func (m *Selection) GetDisputeLockedUntil() time.Time { + if m != nil { + return m.DisputeLockedUntil + } + return time.Time{} +} + +// Pending switch keyed by (outgoing_reporter, selector) in keeper collections. +type PendingSwitchEntry struct { + ToReporter []byte `protobuf:"bytes,1,opt,name=to_reporter,json=toReporter,proto3" json:"to_reporter,omitempty"` + UnlockBlock uint64 `protobuf:"varint,2,opt,name=unlock_block,json=unlockBlock,proto3" json:"unlock_block,omitempty"` +} + +func (m *PendingSwitchEntry) Reset() { *m = PendingSwitchEntry{} } +func (m *PendingSwitchEntry) String() string { return proto.CompactTextString(m) } +func (*PendingSwitchEntry) ProtoMessage() {} +func (*PendingSwitchEntry) Descriptor() ([]byte, []int) { + return fileDescriptor_0b0e998201c9cd64, []int{1} +} +func (m *PendingSwitchEntry) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PendingSwitchEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PendingSwitchEntry.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PendingSwitchEntry) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSwitchEntry.Merge(m, src) +} +func (m *PendingSwitchEntry) XXX_Size() int { + return m.Size() +} +func (m *PendingSwitchEntry) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSwitchEntry.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSwitchEntry proto.InternalMessageInfo + +func (m *PendingSwitchEntry) GetToReporter() []byte { + if m != nil { + return m.ToReporter + } + return nil +} + +func (m *PendingSwitchEntry) GetUnlockBlock() uint64 { + if m != nil { + return m.UnlockBlock + } + return 0 +} + +// Single-reporter summary for O(1) checks in ReporterStake: one Get per reporter +// to see if any pending switch involving this reporter may be ready at height. +type ReporterPendingSwitchHead struct { + OutgoingCount uint32 `protobuf:"varint,1,opt,name=outgoing_count,json=outgoingCount,proto3" json:"outgoing_count,omitempty"` + OutgoingMinUnlock uint64 `protobuf:"varint,2,opt,name=outgoing_min_unlock,json=outgoingMinUnlock,proto3" json:"outgoing_min_unlock,omitempty"` + IncomingCount uint32 `protobuf:"varint,3,opt,name=incoming_count,json=incomingCount,proto3" json:"incoming_count,omitempty"` + IncomingMinUnlock uint64 `protobuf:"varint,4,opt,name=incoming_min_unlock,json=incomingMinUnlock,proto3" json:"incoming_min_unlock,omitempty"` +} + +func (m *ReporterPendingSwitchHead) Reset() { *m = ReporterPendingSwitchHead{} } +func (m *ReporterPendingSwitchHead) String() string { return proto.CompactTextString(m) } +func (*ReporterPendingSwitchHead) ProtoMessage() {} +func (*ReporterPendingSwitchHead) Descriptor() ([]byte, []int) { + return fileDescriptor_0b0e998201c9cd64, []int{2} +} +func (m *ReporterPendingSwitchHead) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ReporterPendingSwitchHead) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ReporterPendingSwitchHead.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ReporterPendingSwitchHead) XXX_Merge(src proto.Message) { + xxx_messageInfo_ReporterPendingSwitchHead.Merge(m, src) +} +func (m *ReporterPendingSwitchHead) XXX_Size() int { + return m.Size() +} +func (m *ReporterPendingSwitchHead) XXX_DiscardUnknown() { + xxx_messageInfo_ReporterPendingSwitchHead.DiscardUnknown(m) +} + +var xxx_messageInfo_ReporterPendingSwitchHead proto.InternalMessageInfo + +func (m *ReporterPendingSwitchHead) GetOutgoingCount() uint32 { + if m != nil { + return m.OutgoingCount + } + return 0 +} + +func (m *ReporterPendingSwitchHead) GetOutgoingMinUnlock() uint64 { + if m != nil { + return m.OutgoingMinUnlock + } + return 0 +} + +func (m *ReporterPendingSwitchHead) GetIncomingCount() uint32 { + if m != nil { + return m.IncomingCount + } + return 0 +} + +func (m *ReporterPendingSwitchHead) GetIncomingMinUnlock() uint64 { + if m != nil { + return m.IncomingMinUnlock + } + return 0 +} + // IndividualDelegation represents a single delegation to a validator type IndividualDelegation struct { // validator_address is the address of the validator @@ -107,7 +250,7 @@ func (m *IndividualDelegation) Reset() { *m = IndividualDelegation{} } func (m *IndividualDelegation) String() string { return proto.CompactTextString(m) } func (*IndividualDelegation) ProtoMessage() {} func (*IndividualDelegation) Descriptor() ([]byte, []int) { - return fileDescriptor_0b0e998201c9cd64, []int{1} + return fileDescriptor_0b0e998201c9cd64, []int{3} } func (m *IndividualDelegation) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -156,13 +299,15 @@ type FormattedSelection struct { DelegationsTotal cosmossdk_io_math.Int `protobuf:"bytes,4,opt,name=delegations_total,json=delegationsTotal,proto3,customtype=cosmossdk.io/math.Int" json:"delegations_total"` // individual_delegations contains details of each delegation (only populated when delegations_count > 1) IndividualDelegations []*IndividualDelegation `protobuf:"bytes,5,rep,name=individual_delegations,json=individualDelegations,proto3" json:"individual_delegations,omitempty"` + // dispute_locked_until — dispute jail only (query surface). + DisputeLockedUntil time.Time `protobuf:"bytes,6,opt,name=dispute_locked_until,json=disputeLockedUntil,proto3,stdtime" json:"dispute_locked_until"` } func (m *FormattedSelection) Reset() { *m = FormattedSelection{} } func (m *FormattedSelection) String() string { return proto.CompactTextString(m) } func (*FormattedSelection) ProtoMessage() {} func (*FormattedSelection) Descriptor() ([]byte, []int) { - return fileDescriptor_0b0e998201c9cd64, []int{2} + return fileDescriptor_0b0e998201c9cd64, []int{4} } func (m *FormattedSelection) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -219,6 +364,13 @@ func (m *FormattedSelection) GetIndividualDelegations() []*IndividualDelegation return nil } +func (m *FormattedSelection) GetDisputeLockedUntil() time.Time { + if m != nil { + return m.DisputeLockedUntil + } + return time.Time{} +} + type SelectorShare struct { SelectorAddress []byte `protobuf:"bytes,1,opt,name=selector_address,json=selectorAddress,proto3" json:"selector_address,omitempty"` Amount cosmossdk_io_math.Int `protobuf:"bytes,2,opt,name=amount,proto3,customtype=cosmossdk.io/math.Int" json:"amount"` @@ -228,7 +380,7 @@ func (m *SelectorShare) Reset() { *m = SelectorShare{} } func (m *SelectorShare) String() string { return proto.CompactTextString(m) } func (*SelectorShare) ProtoMessage() {} func (*SelectorShare) Descriptor() ([]byte, []int) { - return fileDescriptor_0b0e998201c9cd64, []int{3} + return fileDescriptor_0b0e998201c9cd64, []int{5} } func (m *SelectorShare) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -275,7 +427,7 @@ func (m *PeriodRewardData) Reset() { *m = PeriodRewardData{} } func (m *PeriodRewardData) String() string { return proto.CompactTextString(m) } func (*PeriodRewardData) ProtoMessage() {} func (*PeriodRewardData) Descriptor() ([]byte, []int) { - return fileDescriptor_0b0e998201c9cd64, []int{4} + return fileDescriptor_0b0e998201c9cd64, []int{6} } func (m *PeriodRewardData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -329,7 +481,7 @@ func (m *DistributionQueueItem) Reset() { *m = DistributionQueueItem{} } func (m *DistributionQueueItem) String() string { return proto.CompactTextString(m) } func (*DistributionQueueItem) ProtoMessage() {} func (*DistributionQueueItem) Descriptor() ([]byte, []int) { - return fileDescriptor_0b0e998201c9cd64, []int{5} + return fileDescriptor_0b0e998201c9cd64, []int{7} } func (m *DistributionQueueItem) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -381,7 +533,7 @@ func (m *DistributionQueueCounter) Reset() { *m = DistributionQueueCount func (m *DistributionQueueCounter) String() string { return proto.CompactTextString(m) } func (*DistributionQueueCounter) ProtoMessage() {} func (*DistributionQueueCounter) Descriptor() ([]byte, []int) { - return fileDescriptor_0b0e998201c9cd64, []int{6} + return fileDescriptor_0b0e998201c9cd64, []int{8} } func (m *DistributionQueueCounter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -426,6 +578,8 @@ func (m *DistributionQueueCounter) GetTail() uint64 { func init() { proto.RegisterType((*Selection)(nil), "layer.reporter.Selection") + proto.RegisterType((*PendingSwitchEntry)(nil), "layer.reporter.PendingSwitchEntry") + proto.RegisterType((*ReporterPendingSwitchHead)(nil), "layer.reporter.ReporterPendingSwitchHead") proto.RegisterType((*IndividualDelegation)(nil), "layer.reporter.IndividualDelegation") proto.RegisterType((*FormattedSelection)(nil), "layer.reporter.FormattedSelection") proto.RegisterType((*SelectorShare)(nil), "layer.reporter.SelectorShare") @@ -437,49 +591,60 @@ func init() { func init() { proto.RegisterFile("layer/reporter/selection.proto", fileDescriptor_0b0e998201c9cd64) } var fileDescriptor_0b0e998201c9cd64 = []byte{ - // 667 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0x8d, 0x93, 0xb4, 0x6a, 0xb7, 0x29, 0x4d, 0x56, 0x2d, 0x32, 0x41, 0x38, 0x51, 0xc4, 0x21, - 0xa8, 0xaa, 0x2d, 0x0a, 0x37, 0x4e, 0x4d, 0x53, 0xa4, 0x48, 0x1c, 0x8a, 0x53, 0x10, 0x82, 0x83, - 0xb5, 0xb1, 0x07, 0x67, 0x55, 0xdb, 0x1b, 0xad, 0xd7, 0x85, 0x9e, 0xe0, 0xce, 0xa5, 0xdf, 0xc0, - 0x81, 0x2f, 0xe8, 0x47, 0xf4, 0x58, 0xf5, 0x84, 0x38, 0x14, 0xd4, 0xfe, 0x07, 0x42, 0xde, 0xb5, - 0xd3, 0x84, 0x56, 0x20, 0xaa, 0x4a, 0x5c, 0xac, 0xdd, 0x79, 0x33, 0x6f, 0xde, 0xbe, 0x59, 0xdb, - 0xc8, 0x08, 0xc8, 0x3e, 0x70, 0x8b, 0xc3, 0x88, 0x71, 0x01, 0xdc, 0x8a, 0x21, 0x00, 0x57, 0x50, - 0x16, 0x99, 0x23, 0xce, 0x04, 0xc3, 0xb7, 0x24, 0x6e, 0xe6, 0x78, 0xbd, 0x46, 0x42, 0x1a, 0x31, - 0x4b, 0x3e, 0x55, 0x4a, 0xfd, 0x8e, 0xcb, 0xe2, 0x90, 0xc5, 0x8e, 0xdc, 0x59, 0x6a, 0x93, 0x41, - 0xcb, 0x3e, 0xf3, 0x99, 0x8a, 0xa7, 0xab, 0x2c, 0xda, 0xf0, 0x19, 0xf3, 0x03, 0xb0, 0xe4, 0x6e, - 0x90, 0xbc, 0xb5, 0x04, 0x0d, 0x21, 0x16, 0x24, 0x1c, 0xa9, 0x84, 0xd6, 0x17, 0x0d, 0xcd, 0xf7, - 0x73, 0x21, 0xb8, 0x8e, 0xe6, 0xf2, 0xf6, 0xba, 0xd6, 0xd4, 0xda, 0x15, 0x7b, 0xbc, 0xc7, 0xdb, - 0xa8, 0x16, 0x30, 0x77, 0x17, 0x3c, 0x27, 0x89, 0x04, 0x0d, 0x9c, 0x94, 0x49, 0x2f, 0x36, 0xb5, - 0xf6, 0xc2, 0x7a, 0xdd, 0x54, 0x6d, 0xcc, 0xbc, 0x8d, 0xb9, 0x93, 0xb7, 0xe9, 0xcc, 0x1d, 0x9d, - 0x36, 0x0a, 0x07, 0xdf, 0x1b, 0x9a, 0xbd, 0xa4, 0xca, 0x5f, 0xa4, 0xd5, 0x29, 0x8e, 0x57, 0x51, - 0xcd, 0x83, 0x00, 0x7c, 0x92, 0xf6, 0x8e, 0x1d, 0x97, 0x25, 0x91, 0xd0, 0x4b, 0x4d, 0xad, 0x5d, - 0xb6, 0xab, 0x13, 0xc0, 0x66, 0x1a, 0x6f, 0x7d, 0xd6, 0xd0, 0x72, 0x2f, 0xf2, 0xe8, 0x1e, 0xf5, - 0x12, 0x12, 0x74, 0xc7, 0x30, 0xde, 0x42, 0xb5, 0x3d, 0x12, 0x50, 0x8f, 0x08, 0xc6, 0x1d, 0xe2, - 0x79, 0x1c, 0xe2, 0x58, 0x8a, 0x9f, 0xef, 0xe8, 0x27, 0x87, 0x6b, 0xcb, 0x99, 0x4b, 0x1b, 0x0a, - 0xe9, 0x0b, 0x4e, 0x23, 0xdf, 0xae, 0x8e, 0x4b, 0xb2, 0x38, 0xde, 0x44, 0xb3, 0x24, 0x94, 0x0a, - 0x8a, 0xb2, 0x76, 0x35, 0xd5, 0xfd, 0xed, 0xb4, 0xb1, 0xa2, 0xea, 0x63, 0x6f, 0xd7, 0xa4, 0xcc, - 0x0a, 0x89, 0x18, 0x9a, 0xbd, 0x48, 0x9c, 0x1c, 0xae, 0xa1, 0x8c, 0xb8, 0x17, 0x09, 0x3b, 0x2b, - 0x6d, 0x7d, 0x2c, 0x21, 0xfc, 0x94, 0xf1, 0x90, 0x08, 0x01, 0xde, 0x85, 0xad, 0x8f, 0xd1, 0x9c, - 0x1a, 0x36, 0xe3, 0x7f, 0x55, 0x36, 0xce, 0xfc, 0xcf, 0x86, 0xe3, 0x57, 0xd3, 0xc9, 0x82, 0x09, - 0x12, 0xe8, 0xe5, 0x7f, 0xf7, 0x66, 0x92, 0x79, 0x27, 0x25, 0xc1, 0x6f, 0xd0, 0x6d, 0x3a, 0x9e, - 0xa4, 0x33, 0x01, 0xeb, 0x33, 0xcd, 0x52, 0x7b, 0x61, 0xfd, 0xbe, 0x39, 0xfd, 0x26, 0x98, 0x57, - 0xcd, 0xdd, 0x5e, 0xa1, 0x57, 0x44, 0xe3, 0xd6, 0x07, 0xb4, 0xd8, 0xcf, 0x1c, 0xec, 0x0f, 0x09, - 0x07, 0xfc, 0x00, 0x55, 0x73, 0x4b, 0xa7, 0xae, 0x47, 0xc5, 0x5e, 0xca, 0xe3, 0x37, 0x7a, 0x07, - 0x7e, 0x6a, 0xa8, 0xba, 0x0d, 0x9c, 0x32, 0xcf, 0x86, 0x77, 0x84, 0x7b, 0x5d, 0x22, 0x08, 0x7e, - 0x82, 0xe6, 0xf3, 0x66, 0x69, 0xf7, 0xf4, 0x94, 0xf7, 0x7e, 0x3f, 0xe5, 0x94, 0x6c, 0xfb, 0x22, - 0x1f, 0x6f, 0xa0, 0x19, 0xe5, 0xfe, 0x35, 0x54, 0xa9, 0x4a, 0xfc, 0x12, 0x2d, 0x72, 0xa9, 0xc6, - 0xc9, 0x0e, 0x58, 0x92, 0x54, 0x0f, 0x33, 0xaa, 0xbb, 0x97, 0xa9, 0x9e, 0x81, 0x4f, 0xdc, 0xfd, - 0x2e, 0xb8, 0x13, 0x84, 0x5d, 0x70, 0xed, 0x8a, 0xe2, 0xd9, 0x90, 0x34, 0x18, 0xa3, 0xf2, 0x90, - 0xc4, 0x43, 0x79, 0x2f, 0x2a, 0xb6, 0x5c, 0xb7, 0x3e, 0x15, 0xd1, 0x4a, 0x97, 0xc6, 0x82, 0xd3, - 0x41, 0x92, 0xce, 0xe4, 0x79, 0x02, 0x09, 0xf4, 0x04, 0x84, 0x7f, 0xfc, 0xbc, 0x4c, 0x39, 0x54, - 0xbc, 0xae, 0x43, 0xa5, 0x9b, 0x73, 0xa8, 0x7c, 0x23, 0x0e, 0xb5, 0x3a, 0x48, 0xbf, 0x64, 0x86, - 0x7c, 0xc1, 0x80, 0x4b, 0xf7, 0x80, 0x78, 0xd2, 0x8b, 0xb2, 0x2d, 0xd7, 0x69, 0x4c, 0x10, 0xaa, - 0x66, 0x5d, 0xb6, 0xe5, 0xba, 0xb3, 0x75, 0x74, 0x66, 0x68, 0xc7, 0x67, 0x86, 0xf6, 0xe3, 0xcc, - 0xd0, 0x0e, 0xce, 0x8d, 0xc2, 0xf1, 0xb9, 0x51, 0xf8, 0x7a, 0x6e, 0x14, 0x5e, 0xaf, 0xfa, 0x54, - 0x0c, 0x93, 0x81, 0xe9, 0xb2, 0xd0, 0x12, 0x10, 0x04, 0x8c, 0xaf, 0x51, 0x66, 0xa9, 0x1f, 0xcd, - 0xfb, 0x8b, 0x5f, 0x8d, 0xd8, 0x1f, 0x41, 0x3c, 0x98, 0x95, 0x5f, 0x8b, 0x47, 0xbf, 0x02, 0x00, - 0x00, 0xff, 0xff, 0x03, 0x84, 0x93, 0x85, 0x89, 0x06, 0x00, 0x00, + // 846 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xcf, 0x6e, 0xe3, 0x44, + 0x18, 0x8f, 0x93, 0xb4, 0x6a, 0x27, 0xc9, 0x6e, 0x32, 0xa4, 0xc8, 0x0d, 0xda, 0x24, 0x44, 0x20, + 0x05, 0x55, 0xb5, 0xc5, 0xc2, 0x8d, 0x0b, 0xcd, 0xa6, 0x88, 0x48, 0x8b, 0x28, 0xce, 0xee, 0x6a, + 0x05, 0x07, 0x6b, 0x62, 0x0f, 0xce, 0x68, 0xed, 0x99, 0x68, 0x3c, 0xde, 0x25, 0x27, 0x1e, 0x80, + 0xcb, 0x3e, 0x03, 0xcf, 0xb0, 0x0f, 0xc0, 0x71, 0xb9, 0x55, 0x3d, 0x55, 0x1c, 0x0a, 0x6a, 0xdf, + 0x03, 0xa1, 0x99, 0xb1, 0x9d, 0xb8, 0x2d, 0xa0, 0x96, 0x4a, 0x5c, 0xa2, 0xf9, 0xfe, 0xfd, 0x7e, + 0x9f, 0x7f, 0xdf, 0x7c, 0x8e, 0x41, 0x37, 0x44, 0x4b, 0xcc, 0x6d, 0x8e, 0x17, 0x8c, 0x0b, 0xcc, + 0xed, 0x18, 0x87, 0xd8, 0x13, 0x84, 0x51, 0x6b, 0xc1, 0x99, 0x60, 0xf0, 0x9e, 0x8a, 0x5b, 0x59, + 0xbc, 0xd3, 0x42, 0x11, 0xa1, 0xcc, 0x56, 0xbf, 0x3a, 0xa5, 0xb3, 0xeb, 0xb1, 0x38, 0x62, 0xb1, + 0xab, 0x2c, 0x5b, 0x1b, 0x69, 0xa8, 0x1d, 0xb0, 0x80, 0x69, 0xbf, 0x3c, 0xa5, 0xde, 0x5e, 0xc0, + 0x58, 0x10, 0x62, 0x5b, 0x59, 0xb3, 0xe4, 0x7b, 0x5b, 0x90, 0x08, 0xc7, 0x02, 0x45, 0x0b, 0x9d, + 0x30, 0xf8, 0xa5, 0x0c, 0xb6, 0xa7, 0x59, 0x23, 0xb0, 0x03, 0xb6, 0x32, 0x7a, 0xd3, 0xe8, 0x1b, + 0xc3, 0xba, 0x93, 0xdb, 0xf0, 0x08, 0xb4, 0x42, 0xe6, 0xbd, 0xc0, 0xbe, 0x9b, 0x50, 0x41, 0x42, + 0x57, 0x22, 0x99, 0xe5, 0xbe, 0x31, 0xac, 0x3d, 0xec, 0x58, 0x9a, 0xc6, 0xca, 0x68, 0xac, 0x27, + 0x19, 0xcd, 0x68, 0xeb, 0xed, 0x59, 0xaf, 0xf4, 0xfa, 0xf7, 0x9e, 0xe1, 0xdc, 0xd7, 0xe5, 0x4f, + 0x65, 0xb5, 0x8c, 0xc3, 0x3d, 0xd0, 0xf2, 0x71, 0x88, 0x03, 0x24, 0xb9, 0x63, 0xd7, 0x63, 0x09, + 0x15, 0x66, 0xa5, 0x6f, 0x0c, 0xab, 0x4e, 0x73, 0x2d, 0xf0, 0x48, 0xfa, 0xe1, 0xe7, 0xe0, 0x41, + 0xfc, 0x8a, 0x08, 0x6f, 0xee, 0xb2, 0x44, 0xb8, 0x85, 0x4e, 0x66, 0xd2, 0x32, 0xab, 0xaa, 0x70, + 0x57, 0x27, 0x7d, 0x9d, 0x88, 0xc7, 0x2b, 0xb6, 0x91, 0x4c, 0x80, 0xcf, 0x40, 0xdb, 0x27, 0xf1, + 0x22, 0x11, 0xb8, 0x50, 0x6e, 0x6e, 0xdc, 0xe0, 0x19, 0x60, 0x8a, 0xb0, 0x06, 0x3e, 0x78, 0x0e, + 0xe0, 0x11, 0xa6, 0x3e, 0xa1, 0xc1, 0x54, 0x71, 0x1f, 0x52, 0xc1, 0x97, 0xb0, 0x07, 0x6a, 0x82, + 0xb9, 0x97, 0xd4, 0x04, 0x82, 0x39, 0x99, 0x9e, 0xef, 0x83, 0x7a, 0x42, 0x65, 0x23, 0x69, 0xff, + 0x65, 0xd5, 0x7f, 0x4d, 0xfb, 0x54, 0xc7, 0x83, 0x5f, 0x0d, 0xb0, 0x9b, 0xe5, 0x17, 0x28, 0xbe, + 0xc4, 0xc8, 0x87, 0x1f, 0x82, 0x7b, 0x2c, 0x11, 0x01, 0x23, 0x34, 0x48, 0xb5, 0x93, 0x24, 0x0d, + 0xa7, 0x91, 0x79, 0xb5, 0x70, 0x16, 0x78, 0x27, 0x4f, 0x8b, 0x08, 0x75, 0x35, 0x41, 0x4a, 0xd7, + 0xca, 0x42, 0x5f, 0x11, 0xfa, 0x54, 0x05, 0x24, 0x2c, 0xa1, 0x1e, 0x8b, 0x56, 0xb0, 0x15, 0x0d, + 0x9b, 0x79, 0x73, 0xd8, 0x3c, 0x6d, 0x0d, 0x56, 0x4f, 0xa1, 0x95, 0x85, 0x72, 0xd8, 0xc1, 0xcf, + 0x06, 0x68, 0x4f, 0xa8, 0x4f, 0x5e, 0x12, 0x3f, 0x41, 0xe1, 0x38, 0x1f, 0x2f, 0x3c, 0x04, 0xad, + 0x97, 0x28, 0x24, 0x3e, 0x12, 0x8c, 0xbb, 0xc8, 0xf7, 0x39, 0x8e, 0x63, 0xf5, 0x24, 0xdb, 0x23, + 0xf3, 0xe4, 0xcd, 0x7e, 0x3b, 0xbd, 0xe5, 0x07, 0x3a, 0x32, 0x15, 0x9c, 0xd0, 0xc0, 0x69, 0xe6, + 0x25, 0xa9, 0x1f, 0x3e, 0x02, 0x9b, 0x28, 0x52, 0xed, 0x96, 0x55, 0xed, 0x9e, 0x9c, 0xd9, 0x6f, + 0x67, 0xbd, 0x1d, 0x5d, 0x1f, 0xfb, 0x2f, 0x2c, 0xc2, 0xec, 0x08, 0x89, 0xb9, 0x35, 0xa1, 0xe2, + 0xe4, 0xcd, 0x3e, 0x48, 0x81, 0x27, 0x54, 0x38, 0x69, 0xe9, 0xe0, 0xb4, 0x02, 0xe0, 0x17, 0x8c, + 0x47, 0x48, 0x08, 0xec, 0xaf, 0xd6, 0xe2, 0x53, 0xb0, 0xa5, 0x97, 0x95, 0xf1, 0x7f, 0xed, 0x2c, + 0xcf, 0xfc, 0xbf, 0x17, 0xe6, 0x79, 0x31, 0x59, 0x30, 0x81, 0x42, 0x35, 0x9e, 0x1b, 0x6a, 0xb3, + 0x8e, 0xfc, 0x44, 0x82, 0xc0, 0xef, 0xc0, 0xbb, 0x24, 0x9f, 0xa4, 0xbb, 0x16, 0x36, 0x37, 0xfa, + 0x95, 0x61, 0xed, 0xe1, 0x07, 0x56, 0xf1, 0x4d, 0x66, 0x5d, 0x37, 0x77, 0x67, 0x87, 0x5c, 0xe3, + 0x8d, 0xff, 0x76, 0x4b, 0x37, 0xff, 0xe3, 0x96, 0xfe, 0x08, 0x1a, 0xd3, 0x74, 0x32, 0xd3, 0x39, + 0xe2, 0x18, 0x7e, 0x04, 0x9a, 0xd9, 0xa8, 0x0a, 0xd7, 0xae, 0xee, 0xdc, 0xcf, 0xfc, 0x77, 0x7a, + 0xb7, 0xfe, 0x34, 0x40, 0xf3, 0x08, 0x73, 0xc2, 0x7c, 0x07, 0xbf, 0x42, 0xdc, 0x1f, 0x23, 0x81, + 0xe0, 0x67, 0x60, 0x3b, 0x23, 0x93, 0xec, 0x52, 0xbd, 0x07, 0x97, 0xd5, 0x2b, 0xb4, 0xed, 0xac, + 0xf2, 0xe1, 0x01, 0xd8, 0xd0, 0x53, 0xbd, 0x45, 0x57, 0xba, 0x12, 0x3e, 0x03, 0x0d, 0xae, 0xba, + 0x71, 0xd3, 0x07, 0xac, 0x28, 0xa8, 0x8f, 0x53, 0xa8, 0xf7, 0xae, 0x42, 0x3d, 0xc6, 0x01, 0xf2, + 0x96, 0x63, 0xec, 0xad, 0x01, 0x8e, 0xb1, 0xe7, 0xd4, 0x35, 0xce, 0x81, 0x82, 0x81, 0x10, 0x54, + 0xe7, 0x28, 0x9e, 0xab, 0xfb, 0x56, 0x77, 0xd4, 0x79, 0xf0, 0x53, 0x19, 0xec, 0x8c, 0x49, 0x2c, + 0x38, 0x99, 0x25, 0x72, 0xd6, 0xdf, 0x24, 0x38, 0xc1, 0x13, 0x81, 0xa3, 0x7f, 0xfc, 0xdb, 0x29, + 0x28, 0x54, 0xbe, 0xad, 0x42, 0x95, 0xbb, 0x53, 0xa8, 0x7a, 0x27, 0x0a, 0x0d, 0x46, 0xc0, 0xbc, + 0x22, 0x86, 0x5a, 0x5c, 0xcc, 0x95, 0x7a, 0x18, 0xf9, 0x4a, 0x8b, 0xaa, 0xa3, 0xce, 0xd2, 0x27, + 0x10, 0x09, 0xd3, 0xf7, 0xb6, 0x3a, 0x8f, 0x0e, 0xdf, 0x9e, 0x77, 0x8d, 0xe3, 0xf3, 0xae, 0xf1, + 0xc7, 0x79, 0xd7, 0x78, 0x7d, 0xd1, 0x2d, 0x1d, 0x5f, 0x74, 0x4b, 0xa7, 0x17, 0xdd, 0xd2, 0xb7, + 0x7b, 0x01, 0x11, 0xf3, 0x64, 0x66, 0x79, 0x2c, 0xb2, 0x05, 0x0e, 0x43, 0xc6, 0xf7, 0x09, 0xb3, + 0xf5, 0x07, 0xc8, 0x0f, 0xab, 0x4f, 0x10, 0xb1, 0x5c, 0xe0, 0x78, 0xb6, 0xa9, 0x96, 0xe9, 0x93, + 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe6, 0x62, 0x24, 0x1f, 0xa1, 0x08, 0x00, 0x00, } func (m *Selection) Marshal() (dAtA []byte, err error) { @@ -502,17 +667,30 @@ func (m *Selection) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.DisputeLockedUntil, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.DisputeLockedUntil):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintSelection(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x2a + if m.SwitchOutLockedUntilBlock != 0 { + i = encodeVarintSelection(dAtA, i, uint64(m.SwitchOutLockedUntilBlock)) + i-- + dAtA[i] = 0x20 + } if m.DelegationsCount != 0 { i = encodeVarintSelection(dAtA, i, uint64(m.DelegationsCount)) i-- dAtA[i] = 0x18 } - n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LockedUntilTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LockedUntilTime):]) - if err1 != nil { - return 0, err1 + n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LockedUntilTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LockedUntilTime):]) + if err2 != nil { + return 0, err2 } - i -= n1 - i = encodeVarintSelection(dAtA, i, uint64(n1)) + i -= n2 + i = encodeVarintSelection(dAtA, i, uint64(n2)) i-- dAtA[i] = 0x12 if len(m.Reporter) > 0 { @@ -525,6 +703,84 @@ func (m *Selection) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PendingSwitchEntry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PendingSwitchEntry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PendingSwitchEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UnlockBlock != 0 { + i = encodeVarintSelection(dAtA, i, uint64(m.UnlockBlock)) + i-- + dAtA[i] = 0x10 + } + if len(m.ToReporter) > 0 { + i -= len(m.ToReporter) + copy(dAtA[i:], m.ToReporter) + i = encodeVarintSelection(dAtA, i, uint64(len(m.ToReporter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ReporterPendingSwitchHead) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReporterPendingSwitchHead) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ReporterPendingSwitchHead) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IncomingMinUnlock != 0 { + i = encodeVarintSelection(dAtA, i, uint64(m.IncomingMinUnlock)) + i-- + dAtA[i] = 0x20 + } + if m.IncomingCount != 0 { + i = encodeVarintSelection(dAtA, i, uint64(m.IncomingCount)) + i-- + dAtA[i] = 0x18 + } + if m.OutgoingMinUnlock != 0 { + i = encodeVarintSelection(dAtA, i, uint64(m.OutgoingMinUnlock)) + i-- + dAtA[i] = 0x10 + } + if m.OutgoingCount != 0 { + i = encodeVarintSelection(dAtA, i, uint64(m.OutgoingCount)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *IndividualDelegation) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -585,6 +841,14 @@ func (m *FormattedSelection) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n3, err3 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.DisputeLockedUntil, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.DisputeLockedUntil):]) + if err3 != nil { + return 0, err3 + } + i -= n3 + i = encodeVarintSelection(dAtA, i, uint64(n3)) + i-- + dAtA[i] = 0x32 if len(m.IndividualDelegations) > 0 { for iNdEx := len(m.IndividualDelegations) - 1; iNdEx >= 0; iNdEx-- { { @@ -614,12 +878,12 @@ func (m *FormattedSelection) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x18 } - n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LockedUntilTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LockedUntilTime):]) - if err2 != nil { - return 0, err2 + n4, err4 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LockedUntilTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LockedUntilTime):]) + if err4 != nil { + return 0, err4 } - i -= n2 - i = encodeVarintSelection(dAtA, i, uint64(n2)) + i -= n4 + i = encodeVarintSelection(dAtA, i, uint64(n4)) i-- dAtA[i] = 0x12 if len(m.Selector) > 0 { @@ -859,6 +1123,48 @@ func (m *Selection) Size() (n int) { if m.DelegationsCount != 0 { n += 1 + sovSelection(uint64(m.DelegationsCount)) } + if m.SwitchOutLockedUntilBlock != 0 { + n += 1 + sovSelection(uint64(m.SwitchOutLockedUntilBlock)) + } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.DisputeLockedUntil) + n += 1 + l + sovSelection(uint64(l)) + return n +} + +func (m *PendingSwitchEntry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ToReporter) + if l > 0 { + n += 1 + l + sovSelection(uint64(l)) + } + if m.UnlockBlock != 0 { + n += 1 + sovSelection(uint64(m.UnlockBlock)) + } + return n +} + +func (m *ReporterPendingSwitchHead) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.OutgoingCount != 0 { + n += 1 + sovSelection(uint64(m.OutgoingCount)) + } + if m.OutgoingMinUnlock != 0 { + n += 1 + sovSelection(uint64(m.OutgoingMinUnlock)) + } + if m.IncomingCount != 0 { + n += 1 + sovSelection(uint64(m.IncomingCount)) + } + if m.IncomingMinUnlock != 0 { + n += 1 + sovSelection(uint64(m.IncomingMinUnlock)) + } return n } @@ -900,6 +1206,8 @@ func (m *FormattedSelection) Size() (n int) { n += 1 + l + sovSelection(uint64(l)) } } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.DisputeLockedUntil) + n += 1 + l + sovSelection(uint64(l)) return n } @@ -1100,6 +1408,287 @@ func (m *Selection) Unmarshal(dAtA []byte) error { break } } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SwitchOutLockedUntilBlock", wireType) + } + m.SwitchOutLockedUntilBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SwitchOutLockedUntilBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DisputeLockedUntil", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSelection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSelection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.DisputeLockedUntil, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSelection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSelection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PendingSwitchEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PendingSwitchEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PendingSwitchEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ToReporter", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSelection + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSelection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ToReporter = append(m.ToReporter[:0], dAtA[iNdEx:postIndex]...) + if m.ToReporter == nil { + m.ToReporter = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnlockBlock", wireType) + } + m.UnlockBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnlockBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSelection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSelection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReporterPendingSwitchHead) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReporterPendingSwitchHead: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReporterPendingSwitchHead: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OutgoingCount", wireType) + } + m.OutgoingCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OutgoingCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OutgoingMinUnlock", wireType) + } + m.OutgoingMinUnlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OutgoingMinUnlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IncomingCount", wireType) + } + m.IncomingCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IncomingCount |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IncomingMinUnlock", wireType) + } + m.IncomingMinUnlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IncomingMinUnlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipSelection(dAtA[iNdEx:]) @@ -1418,6 +2007,39 @@ func (m *FormattedSelection) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DisputeLockedUntil", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSelection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSelection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSelection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.DisputeLockedUntil, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSelection(dAtA[iNdEx:]) diff --git a/x/reporter/types/selection_lock.go b/x/reporter/types/selection_lock.go new file mode 100644 index 000000000..ffc5abf70 --- /dev/null +++ b/x/reporter/types/selection_lock.go @@ -0,0 +1,8 @@ +package types + +import "time" + +// SelectorStakeLocked returns true when selector stake must be excluded from reporting power. +func SelectorStakeLocked(sel Selection, now time.Time) bool { + return sel.LockedUntilTime.After(now) || sel.DisputeLockedUntil.After(now) +} diff --git a/x/reporter/types/selection_lock_test.go b/x/reporter/types/selection_lock_test.go new file mode 100644 index 000000000..0fb0560d3 --- /dev/null +++ b/x/reporter/types/selection_lock_test.go @@ -0,0 +1,39 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/tellor-io/layer/x/reporter/types" +) + +func TestSelectorStakeLocked(t *testing.T) { + now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) + + t.Run("zero times", func(t *testing.T) { + require.False(t, types.SelectorStakeLocked(types.Selection{}, now)) + }) + + t.Run("locked_until only", func(t *testing.T) { + sel := types.Selection{LockedUntilTime: now.Add(time.Hour)} + require.True(t, types.SelectorStakeLocked(sel, now)) + sel.LockedUntilTime = now.Add(-time.Hour) + require.False(t, types.SelectorStakeLocked(sel, now)) + }) + + t.Run("dispute_locked_until only", func(t *testing.T) { + sel := types.Selection{DisputeLockedUntil: now.Add(time.Hour)} + require.True(t, types.SelectorStakeLocked(sel, now)) + sel.DisputeLockedUntil = now + require.False(t, types.SelectorStakeLocked(sel, now)) + }) + + t.Run("either field locks", func(t *testing.T) { + sel := types.Selection{ + LockedUntilTime: now.Add(-time.Hour), + DisputeLockedUntil: now.Add(time.Hour), + } + require.True(t, types.SelectorStakeLocked(sel, now)) + }) +} diff --git a/x/reporter/types/tx.pb.go b/x/reporter/types/tx.pb.go index 06b1e1a09..c67cf6fee 100644 --- a/x/reporter/types/tx.pb.go +++ b/x/reporter/types/tx.pb.go @@ -500,7 +500,10 @@ var xxx_messageInfo_MsgRemoveSelectorResponse proto.InternalMessageInfo // MsgUnjailReporter defines the Msg/UnjailReporter request type. type MsgUnjailReporter struct { - ReporterAddress string `protobuf:"bytes,1,opt,name=reporter_address,json=reporterAddress,proto3" json:"reporter_address,omitempty"` + // signer_address is the transaction signer (self-unjail when equal to reporter_address). + SignerAddress string `protobuf:"bytes,1,opt,name=signer_address,json=signerAddress,proto3" json:"signer_address,omitempty"` + // reporter_address is the jailed reporter or selector to unjail. + ReporterAddress string `protobuf:"bytes,2,opt,name=reporter_address,json=reporterAddress,proto3" json:"reporter_address,omitempty"` } func (m *MsgUnjailReporter) Reset() { *m = MsgUnjailReporter{} } @@ -779,62 +782,63 @@ func init() { func init() { proto.RegisterFile("layer/reporter/tx.proto", fileDescriptor_67b904a7aa978eb1) } var fileDescriptor_67b904a7aa978eb1 = []byte{ - // 877 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x56, 0xcf, 0x4f, 0xe3, 0x46, - 0x14, 0x8e, 0xb3, 0xdd, 0xad, 0x32, 0xac, 0x02, 0x71, 0x77, 0x97, 0x10, 0x84, 0x03, 0x51, 0xd5, - 0xf2, 0x43, 0x89, 0x05, 0x48, 0x48, 0xe4, 0xd6, 0x00, 0x07, 0xa4, 0xa6, 0xaa, 0x0c, 0xb4, 0xa8, - 0x95, 0x1a, 0x4d, 0xec, 0x91, 0x33, 0x4d, 0xec, 0x49, 0x67, 0x86, 0x40, 0x6e, 0x55, 0x4f, 0x55, - 0x4f, 0xfd, 0x13, 0xb8, 0x54, 0xaa, 0xd4, 0x0b, 0x87, 0x5c, 0x2a, 0xf5, 0x5e, 0x8e, 0x88, 0x53, - 0xd5, 0x03, 0xaa, 0xe0, 0x40, 0xff, 0x83, 0x5e, 0x2b, 0xdb, 0xe3, 0xc4, 0x76, 0x06, 0x28, 0x12, - 0x97, 0x6a, 0x2f, 0x90, 0x99, 0xf7, 0xbd, 0x6f, 0xde, 0xf7, 0xcd, 0xcc, 0x1b, 0x83, 0xe9, 0x0e, - 0xec, 0x23, 0xaa, 0x53, 0xd4, 0x25, 0x94, 0x23, 0xaa, 0xf3, 0x93, 0x4a, 0x97, 0x12, 0x4e, 0xd4, - 0xac, 0x1f, 0xa8, 0x84, 0x81, 0x42, 0x0e, 0x3a, 0xd8, 0x25, 0xba, 0xff, 0x37, 0x80, 0x14, 0x34, - 0x93, 0x30, 0x87, 0x30, 0xbd, 0x09, 0x19, 0xd2, 0x7b, 0xab, 0x4d, 0xc4, 0xe1, 0xaa, 0x6e, 0x12, - 0xec, 0x8a, 0xf8, 0xb4, 0x88, 0x3b, 0xcc, 0xd6, 0x7b, 0xab, 0xde, 0x3f, 0x11, 0x78, 0x5f, 0x04, - 0x18, 0x87, 0x6d, 0xec, 0xda, 0xc3, 0x5c, 0x31, 0x16, 0xa8, 0x99, 0x00, 0xd5, 0xf0, 0x47, 0x7a, - 0x30, 0x10, 0xa1, 0x57, 0x36, 0xb1, 0x49, 0x30, 0xef, 0xfd, 0x12, 0xb3, 0xb3, 0x09, 0x2d, 0x5d, - 0x48, 0xa1, 0x13, 0xa6, 0x2c, 0x24, 0x85, 0x92, 0x36, 0x72, 0x1b, 0x84, 0x62, 0x3b, 0xac, 0xb7, - 0xf4, 0xab, 0x02, 0x26, 0xeb, 0xcc, 0x3e, 0xe8, 0x5a, 0x90, 0xa3, 0x4f, 0xfd, 0x64, 0x75, 0x03, - 0x64, 0xe0, 0x11, 0x6f, 0x11, 0x8a, 0x79, 0x3f, 0xaf, 0xcc, 0x2b, 0x8b, 0x99, 0x5a, 0xfe, 0x72, - 0x50, 0x7e, 0x25, 0xca, 0xf9, 0xc8, 0xb2, 0x28, 0x62, 0x6c, 0x8f, 0x53, 0xec, 0xda, 0xc6, 0x08, - 0xaa, 0x6e, 0x82, 0x17, 0xc1, 0xf2, 0xf9, 0xf4, 0xbc, 0xb2, 0x38, 0xb1, 0xf6, 0xa6, 0x12, 0xf7, - 0xb3, 0x12, 0xf0, 0xd7, 0x32, 0xe7, 0x57, 0xc5, 0xd4, 0xcf, 0xb7, 0x67, 0xcb, 0x8a, 0x21, 0x12, - 0xaa, 0xeb, 0xdf, 0xdd, 0x9e, 0x2d, 0x8f, 0xa8, 0x7e, 0xb8, 0x3d, 0x5b, 0x9e, 0x0f, 0x8a, 0x3f, - 0x19, 0x95, 0x9f, 0xa8, 0xb3, 0x34, 0x03, 0xa6, 0x13, 0x53, 0x06, 0x62, 0x5d, 0xe2, 0x32, 0x54, - 0xfa, 0x3d, 0x0d, 0x72, 0x75, 0x66, 0x6f, 0x51, 0x04, 0x39, 0x32, 0x04, 0x81, 0xba, 0x05, 0xa6, - 0x42, 0xb2, 0x06, 0x0c, 0x54, 0x3c, 0xa8, 0x6f, 0x32, 0xcc, 0x10, 0xd3, 0x6a, 0x03, 0x4c, 0x9a, - 0xc4, 0x71, 0x30, 0x63, 0x98, 0xb8, 0x0d, 0x0a, 0x39, 0xf2, 0xe5, 0x66, 0x6a, 0x1b, 0x9e, 0xac, - 0x3f, 0xaf, 0x8a, 0xb3, 0x01, 0x0f, 0xb3, 0xda, 0x15, 0x4c, 0x74, 0x07, 0xf2, 0x56, 0xe5, 0x63, - 0x64, 0x43, 0xb3, 0xbf, 0x8d, 0xcc, 0xcb, 0x41, 0x19, 0x88, 0x65, 0xb6, 0x91, 0x19, 0x78, 0x90, - 0x1d, 0xd1, 0x19, 0x90, 0x23, 0xf5, 0x4b, 0xf0, 0x9e, 0x83, 0xdd, 0x86, 0xbf, 0x59, 0xac, 0x41, - 0xd1, 0x37, 0x47, 0x98, 0x22, 0x2b, 0xff, 0xcc, 0x5f, 0x64, 0x45, 0x2c, 0xf2, 0x7a, 0x7c, 0x91, - 0x5d, 0x97, 0x47, 0xe8, 0x77, 0x5d, 0x6e, 0xe4, 0x1c, 0xec, 0xee, 0xfb, 0x34, 0x86, 0x60, 0x51, - 0xf3, 0xe0, 0x5d, 0x87, 0xb8, 0xb8, 0x8d, 0x68, 0xfe, 0x1d, 0x8f, 0xd0, 0x08, 0x87, 0xd5, 0xd7, - 0xde, 0x16, 0x8c, 0xf9, 0x53, 0x9a, 0x05, 0x33, 0x63, 0x46, 0x0e, 0x6d, 0x1e, 0x28, 0xbe, 0xcd, - 0x7b, 0xa8, 0x83, 0x4c, 0x1e, 0xb5, 0x99, 0xf9, 0x33, 0xe4, 0x11, 0x36, 0x87, 0x19, 0xa1, 0xcd, - 0xb2, 0xbd, 0x4a, 0x3f, 0x72, 0xaf, 0x84, 0xa6, 0x64, 0x31, 0x42, 0x53, 0xbc, 0xea, 0x31, 0x4d, - 0xc7, 0x98, 0x9b, 0xad, 0xff, 0x9d, 0xa6, 0x58, 0xd5, 0x43, 0x4d, 0x3f, 0x05, 0x9a, 0x0c, 0xe4, - 0x90, 0x1e, 0xda, 0x13, 0xa9, 0xea, 0x26, 0x98, 0x80, 0x6e, 0xff, 0x3f, 0xcb, 0x01, 0xd0, 0xed, - 0x47, 0x94, 0x8c, 0xd9, 0x91, 0x7e, 0xa4, 0x1d, 0xd5, 0x29, 0x4f, 0x49, 0xb4, 0x04, 0x21, 0x22, - 0x5e, 0xe6, 0x50, 0xc4, 0xb1, 0xaf, 0xe1, 0xc0, 0xfd, 0x1a, 0xe2, 0xce, 0x93, 0x5e, 0xe9, 0xea, - 0xdc, 0xf7, 0xa7, 0xc5, 0xd4, 0xdf, 0xa7, 0xc5, 0xd4, 0x7d, 0x57, 0x20, 0xbe, 0xf0, 0xb0, 0xaa, - 0xdf, 0x14, 0x90, 0xad, 0x33, 0xfb, 0x73, 0xcc, 0x5b, 0x16, 0x85, 0xc7, 0xfb, 0xb8, 0xfb, 0x34, - 0x67, 0xe5, 0x13, 0x90, 0xeb, 0xc1, 0x0e, 0xb6, 0xe0, 0xb8, 0xc5, 0x0b, 0x97, 0x83, 0xf2, 0x9c, - 0x60, 0xf9, 0x2c, 0xc4, 0xc4, 0xe9, 0xa6, 0x7a, 0x89, 0xf9, 0xbb, 0x8e, 0x4d, 0x1e, 0xbc, 0x89, - 0x57, 0x3f, 0x14, 0xf6, 0x4b, 0xda, 0x7f, 0x19, 0x76, 0x2c, 0x3c, 0xba, 0xd9, 0x4b, 0x77, 0xb9, - 0xfd, 0xd6, 0xb7, 0xc9, 0xe0, 0x2d, 0x8a, 0x9a, 0x15, 0x1a, 0xb9, 0xf6, 0xcf, 0x73, 0xf0, 0xac, - 0xce, 0x6c, 0xf5, 0x10, 0xbc, 0x8c, 0x3d, 0xb3, 0xc5, 0xe4, 0xf3, 0x98, 0x78, 0xcc, 0x0a, 0x1f, - 0x3e, 0x00, 0x08, 0x57, 0x50, 0xbf, 0x02, 0xd9, 0xc4, 0x4b, 0xb7, 0x20, 0x49, 0x8d, 0x43, 0x0a, - 0x4b, 0x0f, 0x42, 0xa2, 0xfc, 0x89, 0x16, 0x2f, 0xe3, 0x8f, 0x43, 0xa4, 0xfc, 0xf2, 0x96, 0xeb, - 0xf3, 0xc7, 0xdb, 0xad, 0x94, 0x3f, 0x06, 0x91, 0xf3, 0x4b, 0xdb, 0x9f, 0xc7, 0x9f, 0x68, 0x7d, - 0x32, 0xfe, 0x38, 0x44, 0xca, 0x2f, 0xef, 0x4c, 0x1e, 0x7f, 0xa2, 0x2d, 0xc9, 0xf8, 0xe3, 0x10, - 0x29, 0xbf, 0xbc, 0xc7, 0xa8, 0x07, 0x60, 0x22, 0xda, 0x5f, 0x34, 0x49, 0x66, 0x24, 0x5e, 0xf8, - 0xe0, 0xfe, 0xf8, 0x90, 0xf6, 0x10, 0xbc, 0x8c, 0xdd, 0x6e, 0xd9, 0x81, 0x8c, 0x02, 0xa4, 0x07, - 0x52, 0x76, 0xe4, 0x0b, 0xcf, 0xbf, 0xf5, 0xae, 0x6c, 0x6d, 0xe7, 0xfc, 0x5a, 0x53, 0x2e, 0xae, - 0x35, 0xe5, 0xaf, 0x6b, 0x4d, 0xf9, 0xf1, 0x46, 0x4b, 0x5d, 0xdc, 0x68, 0xa9, 0x3f, 0x6e, 0xb4, - 0xd4, 0x17, 0x2b, 0x36, 0xe6, 0xad, 0xa3, 0x66, 0xc5, 0x24, 0x8e, 0xce, 0x51, 0xa7, 0x43, 0x68, - 0x19, 0x13, 0x7d, 0xec, 0x8b, 0x8f, 0xf7, 0xbb, 0x88, 0x35, 0x5f, 0xf8, 0x9f, 0xaa, 0xeb, 0xff, - 0x06, 0x00, 0x00, 0xff, 0xff, 0x70, 0x1d, 0xdb, 0x97, 0xb8, 0x0b, 0x00, 0x00, + // 896 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x56, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xf6, 0xba, 0xb4, 0xc8, 0x93, 0xe2, 0xc4, 0x4b, 0xdb, 0x38, 0xb6, 0x58, 0x27, 0x16, 0x82, + 0x36, 0x95, 0xbd, 0x4a, 0x2b, 0x55, 0x6a, 0x2e, 0x08, 0xb7, 0x3d, 0x54, 0xc2, 0x08, 0x6d, 0x1a, + 0x88, 0x40, 0xc2, 0x1a, 0xef, 0x8e, 0xd6, 0x83, 0xbd, 0x33, 0x66, 0x66, 0xe2, 0xc4, 0x37, 0xc4, + 0x09, 0x71, 0xe2, 0x27, 0xe4, 0x82, 0x84, 0xc4, 0x25, 0x07, 0x0b, 0x09, 0x89, 0x3b, 0x39, 0x46, + 0x39, 0x21, 0x0e, 0x11, 0x4a, 0x0e, 0xe1, 0x1f, 0x70, 0x45, 0xbb, 0x3b, 0xbb, 0xde, 0x5d, 0x4f, + 0x08, 0x96, 0x72, 0x41, 0x5c, 0x12, 0xcf, 0xbc, 0xef, 0x7d, 0xf3, 0xbe, 0xf7, 0x66, 0xde, 0x5b, + 0xb0, 0x3c, 0x80, 0x63, 0xc4, 0x4c, 0x86, 0x86, 0x94, 0x09, 0xc4, 0x4c, 0xb1, 0xdf, 0x1c, 0x32, + 0x2a, 0xa8, 0x5e, 0x0c, 0x0c, 0xcd, 0xc8, 0x50, 0x29, 0x41, 0x0f, 0x13, 0x6a, 0x06, 0x7f, 0x43, + 0x48, 0xc5, 0xb0, 0x29, 0xf7, 0x28, 0x37, 0xbb, 0x90, 0x23, 0x73, 0xb4, 0xd1, 0x45, 0x02, 0x6e, + 0x98, 0x36, 0xc5, 0x44, 0xda, 0x97, 0xa5, 0xdd, 0xe3, 0xae, 0x39, 0xda, 0xf0, 0xff, 0x49, 0xc3, + 0xdb, 0xd2, 0xc0, 0x05, 0xec, 0x63, 0xe2, 0xc6, 0xbe, 0x72, 0x2d, 0x51, 0x2b, 0x21, 0xaa, 0x13, + 0xac, 0xcc, 0x70, 0x21, 0x4d, 0x77, 0x5c, 0xea, 0xd2, 0x70, 0xdf, 0xff, 0x25, 0x77, 0xab, 0x19, + 0x2d, 0x43, 0xc8, 0xa0, 0x17, 0xb9, 0xac, 0x65, 0x85, 0xd2, 0x3e, 0x22, 0x1d, 0xca, 0xb0, 0x1b, + 0xc5, 0x5b, 0xff, 0x59, 0x03, 0x8b, 0x6d, 0xee, 0x6e, 0x0f, 0x1d, 0x28, 0xd0, 0x47, 0x81, 0xb3, + 0xfe, 0x04, 0x14, 0xe0, 0xae, 0xe8, 0x51, 0x86, 0xc5, 0xb8, 0xac, 0xad, 0x6a, 0xf7, 0x0b, 0xad, + 0xf2, 0xc9, 0xa4, 0x71, 0x47, 0x86, 0xf3, 0xbe, 0xe3, 0x30, 0xc4, 0xf9, 0x96, 0x60, 0x98, 0xb8, + 0xd6, 0x14, 0xaa, 0x3f, 0x05, 0xb7, 0xc2, 0xe3, 0xcb, 0xf9, 0x55, 0xed, 0xfe, 0xc2, 0xa3, 0x7b, + 0xcd, 0x74, 0x3e, 0x9b, 0x21, 0x7f, 0xab, 0x70, 0x74, 0x5a, 0xcb, 0xfd, 0x70, 0x71, 0xb8, 0xae, + 0x59, 0xd2, 0x61, 0xf3, 0xf1, 0xd7, 0x17, 0x87, 0xeb, 0x53, 0xaa, 0x6f, 0x2f, 0x0e, 0xd7, 0x57, + 0xc3, 0xe0, 0xf7, 0xa7, 0xe1, 0x67, 0xe2, 0xac, 0xaf, 0x80, 0xe5, 0xcc, 0x96, 0x85, 0xf8, 0x90, + 0x12, 0x8e, 0xea, 0xbf, 0xe6, 0x41, 0xa9, 0xcd, 0xdd, 0x67, 0x0c, 0x41, 0x81, 0x2c, 0x49, 0xa0, + 0x3f, 0x03, 0x4b, 0x11, 0x59, 0x07, 0x86, 0x2a, 0xae, 0xd4, 0xb7, 0x18, 0x79, 0xc8, 0x6d, 0xbd, + 0x03, 0x16, 0x6d, 0xea, 0x79, 0x98, 0x73, 0x4c, 0x49, 0x87, 0x41, 0x81, 0x02, 0xb9, 0x85, 0xd6, + 0x13, 0x5f, 0xd6, 0xef, 0xa7, 0xb5, 0x6a, 0xc8, 0xc3, 0x9d, 0x7e, 0x13, 0x53, 0xd3, 0x83, 0xa2, + 0xd7, 0xfc, 0x00, 0xb9, 0xd0, 0x1e, 0x3f, 0x47, 0xf6, 0xc9, 0xa4, 0x01, 0xe4, 0x31, 0xcf, 0x91, + 0x1d, 0xe6, 0xa0, 0x38, 0xa5, 0xb3, 0xa0, 0x40, 0xfa, 0x67, 0xe0, 0x4d, 0x0f, 0x93, 0x4e, 0x50, + 0x2c, 0xde, 0x61, 0xe8, 0xcb, 0x5d, 0xcc, 0x90, 0x53, 0xbe, 0x11, 0x1c, 0xf2, 0x50, 0x1e, 0x72, + 0x77, 0xf6, 0x90, 0x97, 0x44, 0x24, 0xe8, 0x5f, 0x12, 0x61, 0x95, 0x3c, 0x4c, 0x5e, 0x05, 0x34, + 0x96, 0x64, 0xd1, 0xcb, 0xe0, 0x75, 0x8f, 0x12, 0xdc, 0x47, 0xac, 0xfc, 0x9a, 0x4f, 0x68, 0x45, + 0xcb, 0xcd, 0xbb, 0x7e, 0x09, 0x66, 0xf2, 0x53, 0xaf, 0x82, 0x95, 0x99, 0x44, 0xc6, 0x69, 0x9e, + 0x68, 0x41, 0x9a, 0xb7, 0xd0, 0x00, 0xd9, 0x22, 0x99, 0x66, 0x1e, 0xec, 0xd0, 0x39, 0xd2, 0x1c, + 0x79, 0x44, 0x69, 0x56, 0xd5, 0x2a, 0x3f, 0x67, 0xad, 0xa4, 0xa6, 0x6c, 0x30, 0x52, 0x53, 0x3a, + 0xea, 0x19, 0x4d, 0x7b, 0x58, 0xd8, 0xbd, 0xff, 0x9c, 0xa6, 0x54, 0xd4, 0xb1, 0xa6, 0xef, 0x43, + 0x4d, 0x16, 0xf2, 0xe8, 0x08, 0x6d, 0x49, 0x57, 0xfd, 0x29, 0x58, 0x80, 0x64, 0xfc, 0xaf, 0xe5, + 0x00, 0x48, 0xc6, 0x09, 0x25, 0x33, 0xe9, 0xc8, 0xcf, 0x99, 0x8e, 0xcd, 0x25, 0x5f, 0x49, 0x32, + 0x04, 0x29, 0x22, 0x1d, 0x66, 0x2c, 0xe2, 0xa7, 0x50, 0xc4, 0x36, 0xf9, 0x02, 0xe2, 0x41, 0x5c, + 0x98, 0xf7, 0x40, 0x91, 0x63, 0x97, 0xcc, 0xf1, 0xa2, 0xdf, 0x08, 0xf1, 0xd7, 0x5a, 0x94, 0xea, + 0x37, 0x07, 0xb5, 0xdc, 0x9f, 0x07, 0xb5, 0x9c, 0x2f, 0x29, 0x13, 0x90, 0x54, 0x95, 0x8e, 0x3b, + 0x56, 0xf5, 0x8b, 0x06, 0x8a, 0x6d, 0xee, 0x7e, 0x82, 0x45, 0xcf, 0x61, 0x70, 0xef, 0x15, 0x1e, + 0x5e, 0xcf, 0x5d, 0xfb, 0x10, 0x94, 0x46, 0x70, 0x80, 0x1d, 0x38, 0x5b, 0xa2, 0xb5, 0x93, 0x49, + 0xe3, 0x2d, 0xc9, 0xf2, 0x71, 0x84, 0x49, 0xd3, 0x2d, 0x8d, 0x32, 0xfb, 0x97, 0x5d, 0xbb, 0x32, + 0xb8, 0x97, 0x8e, 0x3e, 0x16, 0xf6, 0x63, 0x3e, 0x98, 0x2c, 0x2f, 0x1c, 0x3c, 0xed, 0x0c, 0x0f, + 0x2e, 0x6b, 0xc0, 0xff, 0xfb, 0x36, 0x1b, 0xce, 0xb2, 0x64, 0xb2, 0xa2, 0x44, 0x3e, 0xfa, 0xeb, + 0x26, 0xb8, 0xd1, 0xe6, 0xae, 0xbe, 0x03, 0x6e, 0xa7, 0xc6, 0x74, 0x2d, 0x3b, 0x5e, 0x33, 0xc3, + 0xb0, 0xf2, 0xee, 0x15, 0x80, 0xe8, 0x04, 0xfd, 0x73, 0x50, 0xcc, 0x4c, 0xca, 0x35, 0x85, 0x6b, + 0x1a, 0x52, 0x79, 0x70, 0x25, 0x24, 0xc9, 0x9f, 0x19, 0x11, 0x2a, 0xfe, 0x34, 0x44, 0xc9, 0xaf, + 0x6e, 0xd9, 0x01, 0x7f, 0xba, 0x5d, 0x2b, 0xf9, 0x53, 0x10, 0x35, 0xbf, 0xb2, 0x7d, 0xfa, 0xfc, + 0x99, 0xd6, 0xa9, 0xe2, 0x4f, 0x43, 0x94, 0xfc, 0xea, 0xce, 0xe6, 0xf3, 0x67, 0xba, 0x9a, 0x8a, + 0x3f, 0x0d, 0x51, 0xf2, 0xab, 0x7b, 0x8c, 0xbe, 0x0d, 0x16, 0x92, 0xfd, 0xc5, 0x50, 0x78, 0x26, + 0xec, 0x95, 0x77, 0xfe, 0xd9, 0x1e, 0xd3, 0xee, 0x80, 0xdb, 0xa9, 0xd7, 0xad, 0xba, 0x90, 0x49, + 0x80, 0xf2, 0x42, 0xaa, 0xae, 0x7c, 0xe5, 0xe6, 0x57, 0xfe, 0x93, 0x6d, 0xbd, 0x38, 0x3a, 0x33, + 0xb4, 0xe3, 0x33, 0x43, 0xfb, 0xe3, 0xcc, 0xd0, 0xbe, 0x3b, 0x37, 0x72, 0xc7, 0xe7, 0x46, 0xee, + 0xb7, 0x73, 0x23, 0xf7, 0xe9, 0x43, 0x17, 0x8b, 0xde, 0x6e, 0xb7, 0x69, 0x53, 0xcf, 0x14, 0x68, + 0x30, 0xa0, 0xac, 0x81, 0xa9, 0x39, 0xf3, 0xc5, 0x28, 0xc6, 0x43, 0xc4, 0xbb, 0xb7, 0x82, 0x4f, + 0xdd, 0xc7, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x90, 0x03, 0x8c, 0xf8, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1538,6 +1542,13 @@ func (m *MsgUnjailReporter) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.ReporterAddress) i = encodeVarintTx(dAtA, i, uint64(len(m.ReporterAddress))) i-- + dAtA[i] = 0x12 + } + if len(m.SignerAddress) > 0 { + i -= len(m.SignerAddress) + copy(dAtA[i:], m.SignerAddress) + i = encodeVarintTx(dAtA, i, uint64(len(m.SignerAddress))) + i-- dAtA[i] = 0xa } return len(dAtA) - i, nil @@ -1855,6 +1866,10 @@ func (m *MsgUnjailReporter) Size() (n int) { } var l int _ = l + l = len(m.SignerAddress) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } l = len(m.ReporterAddress) if l > 0 { n += 1 + l + sovTx(uint64(l)) @@ -2852,6 +2867,38 @@ func (m *MsgUnjailReporter) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignerAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SignerAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ReporterAddress", wireType) }