1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
//! Crate containing various traits used by moondance crates allowing to connect pallet
18
//! with each other or with mocks.
19

            
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
pub mod alias;
23

            
24
pub use {
25
    alias::*,
26
    cumulus_primitives_core::{
27
        relay_chain::{BlockNumber, HeadData, Slot, ValidationCode},
28
        ParaId,
29
    },
30
    dp_chain_state_snapshot::{GenericStateProof, ReadEntryErr},
31
    dp_container_chain_genesis_data::ContainerChainGenesisDataItem,
32
};
33
use {
34
    core::marker::PhantomData,
35
    frame_support::{
36
        dispatch::DispatchErrorWithPostInfo,
37
        pallet_prelude::{Decode, DispatchResultWithPostInfo, Encode, Get, MaxEncodedLen, Weight},
38
        BoundedVec,
39
    },
40
    scale_info::TypeInfo,
41
    serde::{Deserialize, Serialize},
42
    sp_core::H256,
43
    sp_runtime::{
44
        app_crypto::sp_core,
45
        traits::{CheckedAdd, CheckedMul},
46
        ArithmeticError, DispatchResult, Perbill, RuntimeDebug,
47
    },
48
    sp_std::{
49
        collections::{btree_map::BTreeMap, btree_set::BTreeSet},
50
        vec::Vec,
51
    },
52
};
53

            
54
// Separate import as rustfmt wrongly change it to `sp_std::vec::self`, which is the module instead
55
// of the macro.
56
use sp_std::vec;
57

            
58
/// The collator-assignment hook to react to collators being assigned to container chains.
59
pub trait CollatorAssignmentHook<Balance> {
60
    /// This hook is called when collators are assigned to a container
61
    ///
62
    /// The hook should never panic and is required to return the weight consumed.
63
    fn on_collators_assigned(
64
        para_id: ParaId,
65
        maybe_tip: Option<&Balance>,
66
        is_parathread: bool,
67
    ) -> Result<Weight, sp_runtime::DispatchError>;
68
}
69

            
70
#[impl_trait_for_tuples::impl_for_tuples(5)]
71
impl<Balance> CollatorAssignmentHook<Balance> for Tuple {
72
    fn on_collators_assigned(
73
        p: ParaId,
74
        t: Option<&Balance>,
75
        ip: bool,
76
    ) -> Result<Weight, sp_runtime::DispatchError> {
77
        let mut weight: Weight = Default::default();
78
        for_tuples!( #( weight.saturating_accrue(Tuple::on_collators_assigned(p, t, ip)?); )* );
79
        Ok(weight)
80
    }
81
}
82

            
83
/// Container chains collator assignment tip prioritization on congestion.
84
/// Tips paras are willing to pay for collator assignment in case of collators demand
85
/// surpasses the offer.
86
pub trait CollatorAssignmentTip<Balance> {
87
    fn get_para_tip(a: ParaId) -> Option<Balance>;
88
}
89

            
90
impl<Balance> CollatorAssignmentTip<Balance> for () {
91
    fn get_para_tip(_: ParaId) -> Option<Balance> {
92
        None
93
    }
94
}
95

            
96
pub struct AuthorNotingInfo<AccountId> {
97
    pub author: AccountId,
98
    pub block_number: BlockNumber,
99
    pub para_id: ParaId,
100
}
101

            
102
/// The author-noting hook to react to container chains authoring.
103
pub trait AuthorNotingHook<AccountId> {
104
    /// This hook is called partway through the `set_latest_author_data` inherent in author-noting.
105
    ///
106
    /// The hook should never panic and is required to return the weight consumed.
107
    fn on_container_authors_noted(info: &[AuthorNotingInfo<AccountId>]) -> Weight;
108

            
109
    #[cfg(feature = "runtime-benchmarks")]
110
    fn prepare_worst_case_for_bench(author: &AccountId, block_number: BlockNumber, para_id: ParaId);
111
}
112

            
113
#[impl_trait_for_tuples::impl_for_tuples(5)]
114
impl<AccountId> AuthorNotingHook<AccountId> for Tuple {
115
20823
    fn on_container_authors_noted(info: &[AuthorNotingInfo<AccountId>]) -> Weight {
116
20823
        let mut weight: Weight = Default::default();
117
20823
        for_tuples!( #( weight.saturating_accrue(Tuple::on_container_authors_noted(info)); )* );
118
20823
        weight
119
20823
    }
120

            
121
    #[cfg(feature = "runtime-benchmarks")]
122
    fn prepare_worst_case_for_bench(a: &AccountId, b: BlockNumber, p: ParaId) {
123
        for_tuples!( #( Tuple::prepare_worst_case_for_bench(a, b, p); )* );
124
    }
125
}
126

            
127
pub trait DistributeRewards<AccountId, Imbalance> {
128
    fn distribute_rewards(rewarded: AccountId, amount: Imbalance) -> DispatchResultWithPostInfo;
129
}
130

            
131
impl<AccountId, Imbalance> DistributeRewards<AccountId, Imbalance> for () {
132
26
    fn distribute_rewards(_rewarded: AccountId, _amount: Imbalance) -> DispatchResultWithPostInfo {
133
26
        Ok(().into())
134
26
    }
135
}
136

            
137
/// Get the current list of container chains parachain ids.
138
pub trait GetCurrentContainerChains {
139
    type MaxContainerChains: Get<u32>;
140

            
141
    fn current_container_chains() -> BoundedVec<ParaId, Self::MaxContainerChains>;
142

            
143
    #[cfg(feature = "runtime-benchmarks")]
144
    fn set_current_container_chains(container_chains: &[ParaId]);
145
}
146

            
147
#[derive(Copy, Clone, PartialEq, Eq)]
148
pub enum ForSession {
149
    Current,
150
    Next,
151
}
152

            
153
/// Get the current list of container chains parachain ids with its assigned collators.
154
/// It can return a para id with an empty list of collators.
155
pub trait GetContainerChainsWithCollators<AccountId> {
156
    fn container_chains_with_collators(for_session: ForSession) -> Vec<(ParaId, Vec<AccountId>)>;
157

            
158
    #[cfg(feature = "runtime-benchmarks")]
159
    fn set_container_chains_with_collators(
160
        for_session: ForSession,
161
        container_chains: &[(ParaId, Vec<AccountId>)],
162
    );
163
}
164

            
165
/// How often should a parathread collator propose blocks. The units are "1 out of n slots", where the slot time is the
166
/// tanssi slot time, 6 seconds.
167
// TODO: this is currently ignored
168
#[derive(
169
    Clone,
170
    Debug,
171
    Encode,
172
    Decode,
173
2548
    scale_info::TypeInfo,
174
    PartialEq,
175
    Eq,
176
    Serialize,
177
    Deserialize,
178
    MaxEncodedLen,
179
)]
180
pub struct SlotFrequency {
181
    /// The parathread will produce at most 1 block every x slots. min=10 means that collators can produce 1 block
182
    /// every `x >= 10` slots, but they are not enforced to. If collators produce a block after less than 10
183
    /// slots, they will not be rewarded by tanssi.
184
    pub min: u32,
185
    /// The parathread will produce at least 1 block every x slots. max=10 means that collators are forced to
186
    /// produce 1 block every `x <= 10` slots. Collators can produce a block sooner than that if the `min` allows it, but
187
    /// waiting more than 10 slots will make them lose the block reward.
188
    pub max: u32,
189
}
190

            
191
impl SlotFrequency {
192
677
    pub fn should_parathread_buy_core(
193
677
        &self,
194
677
        current_slot: Slot,
195
677
        max_slot_required_to_complete_purchase: Slot,
196
677
        last_block_slot: Slot,
197
677
    ) -> bool {
198
677
        current_slot
199
677
            >= last_block_slot
200
677
                .saturating_add(Slot::from(u64::from(self.min)))
201
677
                .saturating_sub(max_slot_required_to_complete_purchase)
202
677
    }
203

            
204
    pub fn should_parathread_author_block(
205
        &self,
206
        current_slot: Slot,
207
        last_block_slot: Slot,
208
    ) -> bool {
209
        current_slot >= last_block_slot.saturating_add(Slot::from(u64::from(self.min)))
210
    }
211
}
212

            
213
impl Default for SlotFrequency {
214
357
    fn default() -> Self {
215
357
        Self { min: 1, max: 1 }
216
357
    }
217
}
218

            
219
#[derive(
220
    Clone,
221
    Debug,
222
    Encode,
223
    Decode,
224
1274
    scale_info::TypeInfo,
225
    PartialEq,
226
    Eq,
227
    Serialize,
228
    Deserialize,
229
    MaxEncodedLen,
230
)]
231
pub struct ParathreadParams {
232
    pub slot_frequency: SlotFrequency,
233
}
234

            
235
#[derive(Clone, Debug, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)]
236
pub struct SessionContainerChains {
237
    pub parachains: Vec<ParaId>,
238
    pub parathreads: Vec<(ParaId, ParathreadParams)>,
239
}
240

            
241
/// Get the list of container chains parachain ids at given
242
/// session index.
243
pub trait GetSessionContainerChains<SessionIndex> {
244
    fn session_container_chains(session_index: SessionIndex) -> SessionContainerChains;
245
    #[cfg(feature = "runtime-benchmarks")]
246
    fn set_session_container_chains(session_index: SessionIndex, container_chains: &[ParaId]);
247
}
248

            
249
/// Returns author for a parachain id for the given slot.
250
pub trait GetContainerChainAuthor<AccountId> {
251
    fn author_for_slot(slot: Slot, para_id: ParaId) -> Option<AccountId>;
252
    #[cfg(feature = "runtime-benchmarks")]
253
    fn set_authors_for_para_id(para_id: ParaId, authors: Vec<AccountId>);
254
}
255

            
256
/// Returns the host configuration composed of the amount of collators assigned
257
/// to the orchestrator chain, and how many collators are assigned per container chain.
258
pub trait GetHostConfiguration<SessionIndex> {
259
    fn max_collators(session_index: SessionIndex) -> u32;
260
    fn min_collators_for_orchestrator(session_index: SessionIndex) -> u32;
261
    fn max_collators_for_orchestrator(session_index: SessionIndex) -> u32;
262
    fn collators_per_container(session_index: SessionIndex) -> u32;
263
    fn collators_per_parathread(session_index: SessionIndex) -> u32;
264
    fn target_container_chain_fullness(session_index: SessionIndex) -> Perbill;
265
    fn max_parachain_cores_percentage(session_index: SessionIndex) -> Option<Perbill>;
266
    fn full_rotation_mode(session_index: SessionIndex) -> FullRotationModes;
267
    #[cfg(feature = "runtime-benchmarks")]
268
    fn set_host_configuration(_session_index: SessionIndex) {}
269
}
270

            
271
/// Returns current session index.
272
pub trait GetSessionIndex<SessionIndex> {
273
    fn session_index() -> SessionIndex;
274
}
275

            
276
/// Should pallet_collator_assignment trigger a full rotation on this session?
277
pub trait ShouldRotateAllCollators<SessionIndex> {
278
    fn should_rotate_all_collators(session_index: SessionIndex) -> bool;
279
}
280

            
281
impl<SessionIndex> ShouldRotateAllCollators<SessionIndex> for () {
282
    fn should_rotate_all_collators(_session_index: SessionIndex) -> bool {
283
        false
284
    }
285
}
286

            
287
/// Helper trait for pallet_collator_assignment to be able to give priority to invulnerables
288
pub trait RemoveInvulnerables<AccountId> {
289
    /// Remove the first n invulnerables from the list of collators. The order should be respected.
290
    fn remove_invulnerables(
291
        collators: &mut Vec<AccountId>,
292
        num_invulnerables: usize,
293
    ) -> Vec<AccountId>;
294
}
295

            
296
impl<AccountId: Clone> RemoveInvulnerables<AccountId> for () {
297
498
    fn remove_invulnerables(
298
498
        _collators: &mut Vec<AccountId>,
299
498
        _num_invulnerables: usize,
300
498
    ) -> Vec<AccountId> {
301
498
        // Default impl: no collators are invulnerables
302
498
        vec![]
303
498
    }
304
}
305

            
306
/// Helper trait for pallet_collator_assignment to be able to not assign collators to container chains with no credits
307
/// in pallet_services_payment
308
pub trait ParaIdAssignmentHooks<B, AC> {
309
    /// Remove para ids with not enough credits. The resulting order will affect priority: the first para id in the list
310
    /// will be the first one to get collators.
311
    fn pre_assignment(para_ids: &mut Vec<ParaId>, old_assigned: &BTreeSet<ParaId>);
312
    fn post_assignment(
313
        current_assigned: &BTreeSet<ParaId>,
314
        new_assigned: &mut BTreeMap<ParaId, Vec<AC>>,
315
        maybe_tip: &Option<B>,
316
    ) -> Weight;
317

            
318
    /// Make those para ids valid by giving them enough credits, for benchmarking.
319
    #[cfg(feature = "runtime-benchmarks")]
320
    fn make_valid_para_ids(para_ids: &[ParaId]);
321
}
322

            
323
impl<B, AC> ParaIdAssignmentHooks<B, AC> for () {
324
    fn pre_assignment(_para_ids: &mut Vec<ParaId>, _currently_assigned: &BTreeSet<ParaId>) {}
325

            
326
    fn post_assignment(
327
        _current_assigned: &BTreeSet<ParaId>,
328
        _new_assigned: &mut BTreeMap<ParaId, Vec<AC>>,
329
        _maybe_tip: &Option<B>,
330
    ) -> Weight {
331
        Default::default()
332
    }
333

            
334
    #[cfg(feature = "runtime-benchmarks")]
335
    fn make_valid_para_ids(_para_ids: &[ParaId]) {}
336
}
337

            
338
pub trait RelayStorageRootProvider {
339
    fn get_relay_storage_root(relay_block_number: u32) -> Option<H256>;
340

            
341
    #[cfg(feature = "runtime-benchmarks")]
342
    fn set_relay_storage_root(relay_block_number: u32, storage_root: Option<H256>);
343
}
344

            
345
impl RelayStorageRootProvider for () {
346
    fn get_relay_storage_root(_relay_block_number: u32) -> Option<H256> {
347
        None
348
    }
349

            
350
    #[cfg(feature = "runtime-benchmarks")]
351
    fn set_relay_storage_root(_relay_block_number: u32, _storage_root: Option<H256>) {}
352
}
353

            
354
/// Information extracted from the latest container chain header
355
#[derive(
356
    Default,
357
    Clone,
358
    Encode,
359
    Decode,
360
    PartialEq,
361
    sp_core::RuntimeDebug,
362
1764
    scale_info::TypeInfo,
363
    MaxEncodedLen,
364
    Serialize,
365
    Deserialize,
366
)]
367
pub struct ContainerChainBlockInfo<AccountId> {
368
    pub block_number: BlockNumber,
369
    pub author: AccountId,
370
    pub latest_slot_number: Slot,
371
}
372

            
373
pub trait LatestAuthorInfoFetcher<AccountId> {
374
    fn get_latest_author_info(para_id: ParaId) -> Option<ContainerChainBlockInfo<AccountId>>;
375
}
376

            
377
pub trait StorageDeposit<Data, Balance> {
378
    fn compute_deposit(data: &Data) -> Result<Balance, DispatchErrorWithPostInfo>;
379
}
380

            
381
pub struct BytesDeposit<BaseCost, ByteCost>(PhantomData<(BaseCost, ByteCost)>);
382
impl<Data, Balance, BaseCost, ByteCost> StorageDeposit<Data, Balance>
383
    for BytesDeposit<BaseCost, ByteCost>
384
where
385
    Data: Encode,
386
    Balance: TryFrom<usize> + CheckedAdd + CheckedMul,
387
    BaseCost: Get<Balance>,
388
    ByteCost: Get<Balance>,
389
{
390
175
    fn compute_deposit(data: &Data) -> Result<Balance, DispatchErrorWithPostInfo> {
391
175
        let base = BaseCost::get();
392
175
        let byte = ByteCost::get();
393
175
        let size: Balance = data
394
175
            .encoded_size()
395
175
            .try_into()
396
175
            .map_err(|_| ArithmeticError::Overflow)?;
397

            
398
175
        let deposit = byte
399
175
            .checked_mul(&size)
400
175
            .ok_or(ArithmeticError::Overflow)?
401
175
            .checked_add(&base)
402
175
            .ok_or(ArithmeticError::Overflow)?;
403

            
404
175
        Ok(deposit)
405
175
    }
406
}
407

            
408
/// Trait to abstract away relay storage proofs, and allow the same logic to work on both parachains and solochains.
409
/// Parachains should use relay storage proofs, while solochains should read from storage directly.
410
pub trait GenericStorageReader {
411
    fn read_entry<T: Decode>(&self, key: &[u8], fallback: Option<T>) -> Result<T, ReadEntryErr>;
412
}
413

            
414
impl GenericStorageReader for GenericStateProof<cumulus_primitives_core::relay_chain::Block> {
415
21443
    fn read_entry<T: Decode>(&self, key: &[u8], fallback: Option<T>) -> Result<T, ReadEntryErr> {
416
21443
        GenericStateProof::read_entry(self, key, fallback)
417
21443
    }
418
}
419

            
420
/// Solo chain impl, read directly from storage
421
pub struct NativeStorageReader;
422
impl GenericStorageReader for NativeStorageReader {
423
10
    fn read_entry<T: Decode>(&self, key: &[u8], fallback: Option<T>) -> Result<T, ReadEntryErr> {
424
10
        match frame_support::storage::unhashed::get(key).or(fallback) {
425
10
            Some(x) => Ok(x),
426
            None => Err(ReadEntryErr::Absent),
427
        }
428
10
    }
429
}
430

            
431
/// Trait to handle registrar-related operations in a relay-chain context.
432
/// Mostly used to wire Tanssi's and Polkadot's registrars, for them to
433
/// work together in a solo-chain environment.
434
pub trait RegistrarHandler<AccountId> {
435
    fn register(
436
        who: AccountId,
437
        id: ParaId,
438
        genesis_storage: &[ContainerChainGenesisDataItem],
439
        head_data: Option<HeadData>,
440
    ) -> DispatchResult;
441

            
442
    fn schedule_para_upgrade(id: ParaId) -> DispatchResult;
443
    fn schedule_para_downgrade(id: ParaId) -> DispatchResult;
444
    fn deregister(id: ParaId);
445
    fn deregister_weight() -> Weight;
446

            
447
    #[cfg(feature = "runtime-benchmarks")]
448
    fn bench_head_data() -> Option<HeadData> {
449
        None
450
    }
451
    #[cfg(feature = "runtime-benchmarks")]
452
    fn add_trusted_validation_code(_code: Vec<u8>) {}
453
    #[cfg(feature = "runtime-benchmarks")]
454
    fn registrar_new_session(_session: u32) {}
455
    #[cfg(feature = "runtime-benchmarks")]
456
    fn prepare_chain_registration(_id: ParaId, _who: AccountId) {}
457
}
458

            
459
impl<AccountId> RegistrarHandler<AccountId> for () {
460
147
    fn register(
461
147
        _who: AccountId,
462
147
        _id: ParaId,
463
147
        _genesis_storage: &[ContainerChainGenesisDataItem],
464
147
        _head_data: Option<HeadData>,
465
147
    ) -> DispatchResult {
466
147
        Ok(())
467
147
    }
468

            
469
64
    fn schedule_para_upgrade(_id: ParaId) -> DispatchResult {
470
64
        Ok(())
471
64
    }
472

            
473
70
    fn schedule_para_downgrade(_id: ParaId) -> DispatchResult {
474
70
        Ok(())
475
70
    }
476

            
477
42
    fn deregister(_id: ParaId) {}
478

            
479
42
    fn deregister_weight() -> Weight {
480
42
        Weight::default()
481
42
    }
482
}
483

            
484
/// Trait to retrieve the orchestrator block author (if any).
485
/// In a relay-chain context we will return None.
486
pub trait MaybeSelfChainBlockAuthor<AccountId> {
487
    fn get_block_author() -> Option<AccountId>;
488
}
489

            
490
impl<AccountId> MaybeSelfChainBlockAuthor<AccountId> for () {
491
5040
    fn get_block_author() -> Option<AccountId> {
492
5040
        None
493
5040
    }
494
}
495

            
496
/// Information regarding the active era (era in used in session).
497
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
498
pub struct ActiveEraInfo {
499
    /// Index of era.
500
    pub index: EraIndex,
501
    /// Moment of start expressed as millisecond from `$UNIX_EPOCH`.
502
    ///
503
    /// Start can be none if start hasn't been set for the era yet,
504
    /// Start is set on the first on_finalize of the era to guarantee usage of `Time`.
505
    pub start: Option<u64>,
506
}
507

            
508
/// Counter for the number of eras that have passed.
509
pub type EraIndex = u32;
510

            
511
pub trait EraIndexProvider {
512
    fn active_era() -> ActiveEraInfo;
513
    fn era_to_session_start(era_index: EraIndex) -> Option<u32>;
514
}
515

            
516
pub trait ValidatorProvider<ValidatorId> {
517
    fn validators() -> Vec<ValidatorId>;
518
}
519

            
520
pub trait InvulnerablesProvider<ValidatorId> {
521
    fn invulnerables() -> Vec<ValidatorId>;
522
}
523

            
524
pub trait OnEraStart {
525
    fn on_era_start(_era_index: EraIndex, _session_start: u32, _external_idx: u64) {}
526
}
527

            
528
#[impl_trait_for_tuples::impl_for_tuples(5)]
529
impl OnEraStart for Tuple {
530
244
    fn on_era_start(era_index: EraIndex, session_start: u32, external_idx: u64) {
531
244
        for_tuples!( #( Tuple::on_era_start(era_index, session_start, external_idx); )* );
532
244
    }
533
}
534

            
535
pub trait OnEraEnd {
536
    fn on_era_end(_era_index: EraIndex) {}
537
}
538

            
539
#[impl_trait_for_tuples::impl_for_tuples(5)]
540
impl OnEraEnd for Tuple {
541
    fn on_era_end(era_index: EraIndex) {
542
        for_tuples!( #( Tuple::on_era_end(era_index); )* );
543
    }
544
}
545

            
546
/// Strategy to use when rotating collators. Default: rotate all of them. Allows to rotate only a random subset.
547
#[derive(
548
    Clone,
549
    Debug,
550
    Default,
551
    Encode,
552
    Decode,
553
5096
    scale_info::TypeInfo,
554
    PartialEq,
555
    Eq,
556
    Serialize,
557
    Deserialize,
558
    MaxEncodedLen,
559
)]
560
pub enum FullRotationMode {
561
97870
    #[default]
562
    RotateAll,
563
672
    KeepAll,
564
463
    /// Keep this many collators
565
    KeepCollators {
566
        keep: u32,
567
    },
568
469
    /// Keep a ratio of collators wrt to max collators.
569
    /// If max collators changes, the number of collators kept also changes.
570
    KeepPerbill {
571
        percentage: Perbill,
572
    },
573
}
574

            
575
/// Allow to set a different [FullRotationMode] for each kind of chain. Default: rotate all.
576
#[derive(
577
    Clone,
578
    Debug,
579
    Default,
580
    Encode,
581
    Decode,
582
3822
    scale_info::TypeInfo,
583
    PartialEq,
584
    Eq,
585
    Serialize,
586
    Deserialize,
587
    MaxEncodedLen,
588
)]
589
pub struct FullRotationModes {
590
    pub orchestrator: FullRotationMode,
591
    pub parachain: FullRotationMode,
592
    pub parathread: FullRotationMode,
593
}
594

            
595
impl FullRotationModes {
596
    /// Keep all collators assigned to their current chain if possible. This is equivalent to disabling rotation.
597
20403
    pub fn keep_all() -> Self {
598
20403
        Self {
599
20403
            orchestrator: FullRotationMode::KeepAll,
600
20403
            parachain: FullRotationMode::KeepAll,
601
20403
            parathread: FullRotationMode::KeepAll,
602
20403
        }
603
20403
    }
604
}
605

            
606
// A trait to retrieve the external index provider identifying some set of data
607
// In starlight, used to retrieve the external index associated to validators
608
pub trait ExternalIndexProvider {
609
    fn get_external_index() -> u64;
610
}