datahaven-xyz/datahaven · warning · Error

InvalidSlashIndex

InvalidSlashIndex

Error message

InvalidSlashIndex

What it means

Thrown by `cancel_deferred_slash` when the largest provided slash index is out of bounds for the stored `Slashes::<T>::get(era)` vector, i.e. `last_item >= era_slashes.len()`. The extrinsic validates against the highest index first so partial application can never occur.

Solutions

  1. Re-read `Slashes::<T>::get(era)` for the exact era right before building the index list.
  2. Clamp/filter indices to those `< era_slashes.len()` in the caller before submitting.
  3. Avoid re-running a cancellation with previously used indices; track which cancellations already executed.
  4. Double-check the era parameter matches the era whose indices were collected.

Example fix

// before
cancel_deferred_slash(origin, era, indices);
// after
let era_slashes = Slashes::<T>::get(era);
let valid: Vec<_> = indices.into_iter().filter(|i| (*i as usize) < era_slashes.len()).collect();
cancel_deferred_slash(origin, era, valid);
Defensive patterns

Strategy: validation

Validate before calling

const eraSlashes = await api.query.externalValidatorSlashes.slashes(era);
const valid = slashIndices.filter(i => i < eraSlashes.length);
if (valid.length !== slashIndices.length) {
  console.warn('dropping out-of-range slash indices', slashIndices.length - valid.length);
}

Type guard

function indicesInBounds(indices, listLength) {
  return indices.every(i => i >= 0 && i < listLength);
}

Try / catch

try {
  await cancelDeferredSlash(era, indices);
} catch (e) {
  if (String(e).includes('InvalidSlashIndex')) {
    await refetchEraSlashesAndRetry(era);
  }
}

Prevention

When it happens

Trigger: Calling `cancel_deferred_slash` with an index referring to a slash that does not exist for that era — e.g. indices computed for a different era, stale indices after slashes were already cancelled (the vector shrank), or an off-by-one in the caller.

Common situations: Cancelling slashes for era N using indices fetched for era N-1, repeated cancellation runs reusing old indices after the slash list was mutated, or querying storage directly with stale state.

Related errors


AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13). Data as JSON: /api/errors/20e6b68fc4ede75c. Report an issue: GitHub.

Appendix: source

Thrown at operator/pallets/external-validator-slashes/src/lib.rs:383

            // We need to be in the defer period
            ensure!(
                era <= active_era
                    .saturating_add(T::SlashDeferDuration::get().saturating_add(One::one()))
                    && era > active_era,
                Error::<T>::DeferPeriodIsOver
            );

            ensure!(!slash_indices.is_empty(), Error::<T>::EmptyTargets);
            ensure!(
                is_sorted_and_unique(&slash_indices),
                Error::<T>::NotSortedAndUnique
            );
            // fetch slashes for the era in which we want to defer
            let mut era_slashes = Slashes::<T>::get(era);

            let last_item = slash_indices[slash_indices.len().saturating_sub(1)];
            ensure!(
                (last_item as usize) < era_slashes.len(),
                Error::<T>::InvalidSlashIndex
            );

            // Remove elements starting from the highest index to avoid shifting issues.
            for index in slash_indices.into_iter().rev() {
                era_slashes.remove(index as usize);
            }
            // insert back slashes
            Slashes::<T>::insert(era, &era_slashes);
            Ok(())
        }

        #[pallet::call_index(1)]
        #[pallet::weight(T::WeightInfo::force_inject_slash())]
        pub fn force_inject_slash(
            origin: OriginFor<T>,
            era: EraIndex,

View on GitHub (pinned to edcb13dbbc)