-
Notifications
You must be signed in to change notification settings - Fork 3
/
lib.rs
302 lines (253 loc) · 9.62 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
// Copyright (c) 2019 Alain Brenzikofer
// This file is part of Encointer
//
// Encointer is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Encointer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Encointer. If not, see <http://www.gnu.org/licenses/>.
//! # Encointer Scheduler Module
//!
//! provides functionality for
//! - scheduling ceremonies with their different phases
//! - dispatch transition functions upon phase change
#![cfg_attr(not(feature = "std"), no_std)]
use encointer_primitives::scheduler::{CeremonyIndexType, CeremonyPhaseType};
use frame_support::{
dispatch::{DispatchClass, DispatchResult},
traits::{Get, OnTimestampSet},
};
use log::{info, warn};
use sp_runtime::traits::{CheckedDiv, One, Saturating, Zero};
use sp_std::{ops::Rem, prelude::*};
// Logger target
const LOG: &str = "encointer";
pub use crate::weights::WeightInfo;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::{DispatchClass, *};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config]
pub trait Config: frame_system::Config + pallet_timestamp::Config {
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Required origin to interfere with the scheduling (though can always be Root)
type CeremonyMaster: EnsureOrigin<Self::RuntimeOrigin>;
/// Who to inform about ceremony phase change
type OnCeremonyPhaseChange: OnCeremonyPhaseChange;
#[pallet::constant]
type MomentsPerDay: Get<Self::Moment>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Phase changed to `[new phase]`
PhaseChangedTo(CeremonyPhaseType),
CeremonySchedulePushedByOneDay,
}
#[pallet::error]
pub enum Error<T> {
/// a division by zero occurred
DivisionByZero,
}
#[pallet::storage]
#[pallet::getter(fn current_ceremony_index)]
pub(super) type CurrentCeremonyIndex<T: Config> =
StorageValue<_, CeremonyIndexType, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn last_ceremony_block)]
pub(super) type LastCeremonyBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
#[pallet::type_value]
pub(super) fn DefaultForCurrentPhase() -> CeremonyPhaseType {
CeremonyPhaseType::Registering
}
#[pallet::storage]
#[pallet::getter(fn current_phase)]
pub(super) type CurrentPhase<T: Config> =
StorageValue<_, CeremonyPhaseType, ValueQuery, DefaultForCurrentPhase>;
#[pallet::type_value]
pub(super) fn DefaultForNextPhaseTimestamp<T: Config>() -> T::Moment {
T::Moment::zero()
}
#[pallet::storage]
#[pallet::getter(fn next_phase_timestamp)]
pub(super) type NextPhaseTimestamp<T: Config> =
StorageValue<_, T::Moment, ValueQuery, DefaultForNextPhaseTimestamp<T>>;
#[pallet::storage]
#[pallet::getter(fn phase_durations)]
pub(super) type PhaseDurations<T: Config> =
StorageMap<_, Blake2_128Concat, CeremonyPhaseType, T::Moment, ValueQuery>;
#[derive(frame_support::DefaultNoBound)]
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config>
where
<T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize,
{
pub current_ceremony_index: CeremonyIndexType,
pub current_phase: CeremonyPhaseType,
pub phase_durations: Vec<(CeremonyPhaseType, T::Moment)>,
#[serde(skip)]
pub _config: sp_std::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T>
where
<T as pallet_timestamp::Config>::Moment: MaybeSerializeDeserialize,
{
fn build(&self) {
<CurrentCeremonyIndex<T>>::put(self.current_ceremony_index);
<CurrentPhase<T>>::put(self.current_phase);
self.phase_durations.iter().for_each(|(k, v)| {
<PhaseDurations<T>>::insert(k, v);
});
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Manually transition to next phase without affecting the ceremony rhythm
///
/// May only be called from `T::CeremonyMaster`.
#[pallet::call_index(0)]
#[pallet::weight((<T as Config>::WeightInfo::next_phase(), DispatchClass::Normal, Pays::Yes))]
pub fn next_phase(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
T::CeremonyMaster::ensure_origin(origin)?;
Self::progress_phase()?;
Ok(().into())
}
/// Push next phase change by one entire day
///
/// May only be called from `T::CeremonyMaster`.
#[pallet::call_index(1)]
#[pallet::weight((<T as Config>::WeightInfo::push_by_one_day(), DispatchClass::Normal, Pays::Yes))]
pub fn push_by_one_day(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
T::CeremonyMaster::ensure_origin(origin)?;
let tnext = Self::next_phase_timestamp().saturating_add(T::MomentsPerDay::get());
<NextPhaseTimestamp<T>>::put(tnext);
Self::deposit_event(Event::CeremonySchedulePushedByOneDay);
Ok(().into())
}
#[pallet::call_index(2)]
#[pallet::weight((<T as Config>::WeightInfo::set_phase_duration(), DispatchClass::Normal, Pays::Yes))]
pub fn set_phase_duration(
origin: OriginFor<T>,
ceremony_phase: CeremonyPhaseType,
duration: T::Moment,
) -> DispatchResultWithPostInfo {
T::CeremonyMaster::ensure_origin(origin)?;
<PhaseDurations<T>>::insert(ceremony_phase, duration);
Ok(().into())
}
#[pallet::call_index(3)]
#[pallet::weight((<T as Config>::WeightInfo::set_next_phase_timestamp(), DispatchClass::Normal, Pays::Yes))]
pub fn set_next_phase_timestamp(
origin: OriginFor<T>,
timestamp: T::Moment,
) -> DispatchResultWithPostInfo {
T::CeremonyMaster::ensure_origin(origin)?;
<NextPhaseTimestamp<T>>::put(timestamp);
Ok(().into())
}
}
}
impl<T: Config> Pallet<T> {
// implicitly assuming Moment to be unix epoch!
fn progress_phase() -> DispatchResult {
let current_phase = <CurrentPhase<T>>::get();
let current_ceremony_index = <CurrentCeremonyIndex<T>>::get();
let last_phase_timestamp = Self::next_phase_timestamp();
let next_phase = match current_phase {
CeremonyPhaseType::Registering => CeremonyPhaseType::Assigning,
CeremonyPhaseType::Assigning => CeremonyPhaseType::Attesting,
CeremonyPhaseType::Attesting => {
let next_ceremony_index = current_ceremony_index.saturating_add(1);
<CurrentCeremonyIndex<T>>::put(next_ceremony_index);
info!(target: LOG, "new ceremony phase with index {}", next_ceremony_index);
CeremonyPhaseType::Registering
},
};
let next = last_phase_timestamp.saturating_add(<PhaseDurations<T>>::get(next_phase));
Self::resync_and_set_next_phase_timestamp(next)?;
<CurrentPhase<T>>::put(next_phase);
T::OnCeremonyPhaseChange::on_ceremony_phase_change(next_phase);
Self::deposit_event(Event::PhaseChangedTo(next_phase));
info!(target: LOG, "phase changed to: {:?}", next_phase);
Ok(())
}
pub fn get_cycle_duration() -> T::Moment {
<PhaseDurations<T>>::get(CeremonyPhaseType::Registering) +
<PhaseDurations<T>>::get(CeremonyPhaseType::Assigning) +
<PhaseDurations<T>>::get(CeremonyPhaseType::Attesting)
}
// we need to resync in two situations:
// 1. when the chain bootstraps and cycle duration is smaller than 24h, phases would cycle with
// every block until catched up
// 2. when next_phase() is used, we would introduce long idle phases because
// next_phase_timestamp would be pushed furhter and further into the future
fn resync_and_set_next_phase_timestamp(tnext: T::Moment) -> DispatchResult {
let cycle_duration = Self::get_cycle_duration();
let now = pallet_timestamp::Now::<T>::get();
let tnext = if tnext < now {
let gap = now - tnext;
if let Some(n) = gap.checked_div(&cycle_duration) {
tnext.saturating_add((cycle_duration).saturating_mul(n + T::Moment::one()))
} else {
return Err(<Error<T>>::DivisionByZero.into());
}
} else {
let gap = tnext - now;
if let Some(n) = gap.checked_div(&cycle_duration) {
tnext.saturating_sub(cycle_duration.saturating_mul(n))
} else {
return Err(<Error<T>>::DivisionByZero.into());
}
};
<NextPhaseTimestamp<T>>::put(tnext);
info!(target: LOG, "next phase change at: {:?}", tnext);
Ok(())
}
fn on_timestamp_set(now: T::Moment) {
if Self::next_phase_timestamp() == T::Moment::zero() {
// only executed in first block after genesis.
// in case we upgrade from a runtime that didn't have this pallet or other curiosities
if <CurrentCeremonyIndex<T>>::get() == 0 {
<CurrentCeremonyIndex<T>>::put(1);
}
// set phase start to 0:00 UTC on the day of genesis
let next = (now - now.rem(T::MomentsPerDay::get()))
.saturating_add(<PhaseDurations<T>>::get(CeremonyPhaseType::Registering));
if Self::resync_and_set_next_phase_timestamp(next).is_err() {
warn!(target: LOG, "resync ceremony phase failed");
};
} else if Self::next_phase_timestamp() < now && Self::progress_phase().is_err() {
warn!(target: LOG, "progress ceremony phase failed");
};
}
}
impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
fn on_timestamp_set(moment: T::Moment) {
Self::on_timestamp_set(moment)
}
}
/// An event handler for when the ceremony phase changes.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnCeremonyPhaseChange {
fn on_ceremony_phase_change(new_phase: CeremonyPhaseType);
}
mod weights;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;