subxt_core/utils/
era.rs

1// Copyright 2019-2024 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5use scale_decode::DecodeAsType;
6use scale_encode::EncodeAsType;
7
8// Dev note: This and related bits taken from `sp_runtime::generic::Era`
9/// An era to describe the longevity of a transaction.
10#[derive(
11    PartialEq,
12    Default,
13    Eq,
14    Clone,
15    Copy,
16    Debug,
17    serde::Serialize,
18    serde::Deserialize,
19    DecodeAsType,
20    EncodeAsType,
21    scale_info::TypeInfo,
22)]
23pub enum Era {
24    /// The transaction is valid forever. The genesis hash must be present in the signed content.
25    #[default]
26    Immortal,
27
28    /// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
29    ///
30    /// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
31    /// of `system` module.
32    Mortal {
33        /// The number of blocks that the tx will be valid for after the checkpoint block
34        /// hash found in the signer payload.
35        period: u64,
36        /// The phase in the period that this transaction's lifetime begins (and, importantly,
37        /// implies which block hash is included in the signature material). If the `period` is
38        /// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
39        /// `period` is.
40        phase: u64,
41    },
42}
43
44// E.g. with period == 4:
45// 0         10        20        30        40
46// 0123456789012345678901234567890123456789012
47//              |...|
48//    authored -/   \- expiry
49// phase = 1
50// n = Q(current - phase, period) + phase
51impl Era {
52    /// Create a new era based on a period (which should be a power of two between 4 and 65536
53    /// inclusive) and a block number on which it should start (or, for long periods, be shortly
54    /// after the start).
55    ///
56    /// If using `Era` in the context of `FRAME` runtime, make sure that `period`
57    /// does not exceed `BlockHashCount` parameter passed to `system` module, since that
58    /// prunes old blocks and renders transactions immediately invalid.
59    pub fn mortal(period: u64, current: u64) -> Self {
60        let period = period
61            .checked_next_power_of_two()
62            .unwrap_or(1 << 16)
63            .clamp(4, 1 << 16);
64        let phase = current % period;
65        let quantize_factor = (period >> 12).max(1);
66        let quantized_phase = phase / quantize_factor * quantize_factor;
67
68        Self::Mortal {
69            period,
70            phase: quantized_phase,
71        }
72    }
73}
74
75// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
76// it's really the most important bit here.
77impl codec::Encode for Era {
78    fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
79        match self {
80            Self::Immortal => output.push_byte(0),
81            Self::Mortal { period, phase } => {
82                let quantize_factor = (*period >> 12).max(1);
83                let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
84                    | ((phase / quantize_factor) << 4) as u16;
85                encoded.encode_to(output);
86            }
87        }
88    }
89}
90impl codec::Decode for Era {
91    fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
92        let first = input.read_byte()?;
93        if first == 0 {
94            Ok(Self::Immortal)
95        } else {
96            let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
97            let period = 2 << (encoded % (1 << 4));
98            let quantize_factor = (period >> 12).max(1);
99            let phase = (encoded >> 4) * quantize_factor;
100            if period >= 4 && phase < period {
101                Ok(Self::Mortal { period, phase })
102            } else {
103                Err("Invalid period and phase".into())
104            }
105        }
106    }
107}