sp_runtime/generic/
era.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Generic implementation of an unchecked (pre-verification) extrinsic.
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23use crate::codec::{Decode, DecodeWithMemTracking, Encode, Error, Input, Output};
24
25/// Era period
26pub type Period = u64;
27
28/// Era phase
29pub type Phase = u64;
30
31/// An era to describe the longevity of a transaction.
32#[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, Copy, sp_core::RuntimeDebug)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34pub enum Era {
35	/// The transaction is valid forever. The genesis hash must be present in the signed content.
36	Immortal,
37
38	/// Period and phase are encoded:
39	/// - The period of validity from the block hash found in the signing material.
40	/// - The phase in the period that this transaction's lifetime begins (and, importantly,
41	/// implies which block hash is included in the signature material). If the `period` is
42	/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
43	/// `period` is.
44	///
45	/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
46	/// of `system` module.
47	Mortal(Period, Phase),
48}
49
50// E.g. with period == 4:
51// 0         10        20        30        40
52// 0123456789012345678901234567890123456789012
53//              |...|
54//    authored -/   \- expiry
55// phase = 1
56// n = Q(current - phase, period) + phase
57impl Era {
58	/// Create a new era based on a period (which should be a power of two between 4 and 65536
59	/// inclusive) and a block number on which it should start (or, for long periods, be shortly
60	/// after the start).
61	///
62	/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
63	/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
64	/// prunes old blocks and renders transactions immediately invalid.
65	pub fn mortal(period: u64, current: u64) -> Self {
66		let period = period.checked_next_power_of_two().unwrap_or(1 << 16).clamp(4, 1 << 16);
67		let phase = current % period;
68		let quantize_factor = (period >> 12).max(1);
69		let quantized_phase = phase / quantize_factor * quantize_factor;
70
71		Self::Mortal(period, quantized_phase)
72	}
73
74	/// Create an "immortal" transaction.
75	pub fn immortal() -> Self {
76		Self::Immortal
77	}
78
79	/// `true` if this is an immortal transaction.
80	pub fn is_immortal(&self) -> bool {
81		matches!(self, Self::Immortal)
82	}
83
84	/// Get the block number of the start of the era whose properties this object
85	/// describes that `current` belongs to.
86	pub fn birth(self, current: u64) -> u64 {
87		match self {
88			Self::Immortal => 0,
89			Self::Mortal(period, phase) => (current.max(phase) - phase) / period * period + phase,
90		}
91	}
92
93	/// Get the block number of the first block at which the era has ended.
94	pub fn death(self, current: u64) -> u64 {
95		match self {
96			Self::Immortal => u64::MAX,
97			Self::Mortal(period, _) => self.birth(current) + period,
98		}
99	}
100}
101
102impl Encode for Era {
103	fn encode_to<T: Output + ?Sized>(&self, output: &mut T) {
104		match self {
105			Self::Immortal => output.push_byte(0),
106			Self::Mortal(period, phase) => {
107				let quantize_factor = (*period as u64 >> 12).max(1);
108				let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16 |
109					((phase / quantize_factor) << 4) as u16;
110				encoded.encode_to(output);
111			},
112		}
113	}
114}
115
116impl codec::EncodeLike for Era {}
117
118impl Decode for Era {
119	fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
120		let first = input.read_byte()?;
121		if first == 0 {
122			Ok(Self::Immortal)
123		} else {
124			let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
125			let period = 2 << (encoded % (1 << 4));
126			let quantize_factor = (period >> 12).max(1);
127			let phase = (encoded >> 4) * quantize_factor;
128			if period >= 4 && phase < period {
129				Ok(Self::Mortal(period, phase))
130			} else {
131				Err("Invalid period and phase".into())
132			}
133		}
134	}
135}
136
137/// Add Mortal{N}(u8) variants with the given indices, to describe custom encoding.
138macro_rules! mortal_variants {
139    ($variants:ident, $($index:literal),* ) => {
140		$variants
141		$(
142			.variant(concat!(stringify!(Mortal), stringify!($index)), |v| v
143				.index($index)
144				.fields(scale_info::build::Fields::unnamed().field(|f| f.ty::<u8>()))
145			)
146		)*
147    }
148}
149
150impl scale_info::TypeInfo for Era {
151	type Identity = Self;
152
153	fn type_info() -> scale_info::Type {
154		let variants = scale_info::build::Variants::new().variant("Immortal", |v| v.index(0));
155
156		// this is necessary since the size of the encoded Mortal variant is `u16`, conditional on
157		// the value of the first byte being > 0.
158		let variants = mortal_variants!(
159			variants, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
160			22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
161			44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
162			66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
163			88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
164			108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
165			125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
166			142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158,
167			159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
168			176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192,
169			193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
170			210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
171			227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
172			244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
173		);
174
175		scale_info::Type::builder()
176			.path(scale_info::Path::new("Era", module_path!()))
177			.variant(variants)
178	}
179}
180
181#[cfg(test)]
182mod tests {
183	use super::*;
184
185	#[test]
186	fn immortal_works() {
187		let e = Era::immortal();
188		assert_eq!(e.birth(0), 0);
189		assert_eq!(e.death(0), u64::MAX);
190		assert_eq!(e.birth(1), 0);
191		assert_eq!(e.death(1), u64::MAX);
192		assert_eq!(e.birth(u64::MAX), 0);
193		assert_eq!(e.death(u64::MAX), u64::MAX);
194		assert!(e.is_immortal());
195
196		assert_eq!(e.encode(), vec![0u8]);
197		assert_eq!(e, Era::decode(&mut &[0u8][..]).unwrap());
198	}
199
200	#[test]
201	fn mortal_codec_works() {
202		let e = Era::mortal(64, 42);
203		assert!(!e.is_immortal());
204
205		let expected = vec![5 + 42 % 16 * 16, 42 / 16];
206		assert_eq!(e.encode(), expected);
207		assert_eq!(e, Era::decode(&mut &expected[..]).unwrap());
208	}
209
210	#[test]
211	fn long_period_mortal_codec_works() {
212		let e = Era::mortal(32768, 20000);
213
214		let expected = vec![(14 + 2500 % 16 * 16) as u8, (2500 / 16) as u8];
215		assert_eq!(e.encode(), expected);
216		assert_eq!(e, Era::decode(&mut &expected[..]).unwrap());
217	}
218
219	#[test]
220	fn era_initialization_works() {
221		assert_eq!(Era::mortal(64, 42), Era::Mortal(64, 42));
222		assert_eq!(Era::mortal(32768, 20000), Era::Mortal(32768, 20000));
223		assert_eq!(Era::mortal(200, 513), Era::Mortal(256, 1));
224		assert_eq!(Era::mortal(2, 1), Era::Mortal(4, 1));
225		assert_eq!(Era::mortal(4, 5), Era::Mortal(4, 1));
226	}
227
228	#[test]
229	fn quantized_clamped_era_initialization_works() {
230		// clamp 1000000 to 65536, quantize 1000001 % 65536 to the nearest 4
231		assert_eq!(Era::mortal(1000000, 1000001), Era::Mortal(65536, 1000001 % 65536 / 4 * 4));
232	}
233
234	#[test]
235	fn mortal_birth_death_works() {
236		let e = Era::mortal(4, 6);
237		for i in 6..10 {
238			assert_eq!(e.birth(i), 6);
239			assert_eq!(e.death(i), 10);
240		}
241
242		// wrong because it's outside of the (current...current + period) range
243		assert_ne!(e.birth(10), 6);
244		assert_ne!(e.birth(5), 6);
245	}
246
247	#[test]
248	fn current_less_than_phase() {
249		// should not panic
250		Era::mortal(4, 3).birth(1);
251	}
252}