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 alloc::{format, vec::Vec};
6use codec::{Decode, Encode};
7use scale_decode::{
8    IntoVisitor, TypeResolver, Visitor,
9    ext::scale_type_resolver,
10    visitor::{TypeIdFor, types::Composite, types::Variant},
11};
12use scale_encode::EncodeAsType;
13
14// Dev note: This and related bits taken from `sp_runtime::generic::Era`
15/// An era to describe the longevity of a transaction.
16#[derive(
17    PartialEq,
18    Default,
19    Eq,
20    Clone,
21    Copy,
22    Debug,
23    serde::Serialize,
24    serde::Deserialize,
25    scale_info::TypeInfo,
26)]
27pub enum Era {
28    /// The transaction is valid forever. The genesis hash must be present in the signed content.
29    #[default]
30    Immortal,
31
32    /// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
33    ///
34    /// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
35    /// of `system` module.
36    Mortal {
37        /// The number of blocks that the tx will be valid for after the checkpoint block
38        /// hash found in the signer payload.
39        period: u64,
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        phase: u64,
45    },
46}
47
48// E.g. with period == 4:
49// 0         10        20        30        40
50// 0123456789012345678901234567890123456789012
51//              |...|
52//    authored -/   \- expiry
53// phase = 1
54// n = Q(current - phase, period) + phase
55impl Era {
56    /// Create a new era based on a period (which should be a power of two between 4 and 65536
57    /// inclusive) and a block number on which it should start (or, for long periods, be shortly
58    /// after the start).
59    ///
60    /// If using `Era` in the context of `FRAME` runtime, make sure that `period`
61    /// does not exceed `BlockHashCount` parameter passed to `system` module, since that
62    /// prunes old blocks and renders transactions immediately invalid.
63    pub fn mortal(period: u64, current: u64) -> Self {
64        let period = period
65            .checked_next_power_of_two()
66            .unwrap_or(1 << 16)
67            .clamp(4, 1 << 16);
68        let phase = current % period;
69        let quantize_factor = (period >> 12).max(1);
70        let quantized_phase = phase / quantize_factor * quantize_factor;
71
72        Self::Mortal {
73            period,
74            phase: quantized_phase,
75        }
76    }
77}
78
79// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
80// it's really the most important bit here.
81impl codec::Encode for Era {
82    fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
83        match self {
84            Self::Immortal => output.push_byte(0),
85            Self::Mortal { period, phase } => {
86                let quantize_factor = (*period >> 12).max(1);
87                let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
88                    | ((phase / quantize_factor) << 4) as u16;
89                encoded.encode_to(output);
90            }
91        }
92    }
93}
94impl codec::Decode for Era {
95    fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
96        let first = input.read_byte()?;
97        if first == 0 {
98            Ok(Self::Immortal)
99        } else {
100            let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
101            let period = 2 << (encoded % (1 << 4));
102            let quantize_factor = (period >> 12).max(1);
103            let phase = (encoded >> 4) * quantize_factor;
104            if period >= 4 && phase < period {
105                Ok(Self::Mortal { period, phase })
106            } else {
107                Err("Invalid period and phase".into())
108            }
109        }
110    }
111}
112
113/// Define manually how to encode an Era given some type information. Here we
114/// basically check that the type we're targeting is called "Era" and then codec::Encode.
115impl EncodeAsType for Era {
116    fn encode_as_type_to<R: TypeResolver>(
117        &self,
118        type_id: R::TypeId,
119        types: &R,
120        out: &mut Vec<u8>,
121    ) -> Result<(), scale_encode::Error> {
122        // Visit the type to check that it is an Era. This is only a rough check.
123        let visitor = scale_type_resolver::visitor::new((), |_, _| false)
124            .visit_variant(|_, path, _variants| path.last() == Some("Era"));
125
126        let is_era = types
127            .resolve_type(type_id.clone(), visitor)
128            .unwrap_or_default();
129        if !is_era {
130            return Err(scale_encode::Error::custom_string(format!(
131                "Type {type_id:?} is not a valid Era type; expecting either Immortal or MortalX variant"
132            )));
133        }
134
135        // if the type looks valid then just scale encode our Era.
136        self.encode_to(out);
137        Ok(())
138    }
139}
140
141/// Define manually how to decode an Era given some type information. Here we check that the
142/// variant we're decoding is one of the expected Era variants, and that the field is correct if so,
143/// ensuring that this will fail if trying to decode something that isn't an Era.
144pub struct EraVisitor<R>(core::marker::PhantomData<R>);
145
146impl IntoVisitor for Era {
147    type AnyVisitor<R: TypeResolver> = EraVisitor<R>;
148    fn into_visitor<R: TypeResolver>() -> Self::AnyVisitor<R> {
149        EraVisitor(core::marker::PhantomData)
150    }
151}
152
153impl<R: TypeResolver> Visitor for EraVisitor<R> {
154    type Value<'scale, 'resolver> = Era;
155    type Error = scale_decode::Error;
156    type TypeResolver = R;
157
158    // Unwrap any newtype wrappers around the era, eg the CheckMortality extension (which actually
159    // has 2 fields, but scale_info seems to automatically ignore the PhantomData field). This
160    // allows us to decode directly from CheckMortality into Era.
161    fn visit_composite<'scale, 'resolver>(
162        self,
163        value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
164        _type_id: TypeIdFor<Self>,
165    ) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
166        if value.remaining() != 1 {
167            return Err(scale_decode::Error::custom_string(format!(
168                "Expected any wrapper around Era to have exactly one field, but got {} fields",
169                value.remaining()
170            )));
171        }
172
173        value
174            .decode_item(self)
175            .expect("1 field expected; checked above.")
176    }
177
178    fn visit_variant<'scale, 'resolver>(
179        self,
180        value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
181        _type_id: TypeIdFor<Self>,
182    ) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
183        let variant = value.name();
184
185        // If the variant is immortal, we know the outcome.
186        if variant == "Immortal" {
187            return Ok(Era::Immortal);
188        }
189
190        // Otherwise, we expect a variant Mortal1..Mortal255 where the number
191        // here is the first byte, and the second byte is conceptually a field of this variant.
192        // This weird encoding is because the Era is compressed to just 1 byte if immortal and
193        // just 2 bytes if mortal.
194        //
195        // Note: We _could_ just assume we'll have 2 bytes to work with and decode the era directly,
196        // but checking the variant names ensures that the thing we think is an Era actually _is_
197        // one, based on the type info for it.
198        let first_byte = variant
199            .strip_prefix("Mortal")
200            .and_then(|s| s.parse::<u8>().ok())
201            .ok_or_else(|| {
202                scale_decode::Error::custom_string(format!(
203                    "Expected MortalX variant, but got {variant}"
204                ))
205            })?;
206
207        // We need 1 field in the MortalN variant containing the second byte.
208        let mortal_fields = value.fields();
209        if mortal_fields.remaining() != 1 {
210            return Err(scale_decode::Error::custom_string(format!(
211                "Expected Mortal{} to have one u8 field, but got {} fields",
212                first_byte,
213                mortal_fields.remaining()
214            )));
215        }
216
217        let second_byte = mortal_fields
218            .decode_item(u8::into_visitor())
219            .expect("At least one field should exist; checked above.")
220            .map_err(|e| {
221                scale_decode::Error::custom_string(format!(
222                    "Expected mortal variant field to be u8, but: {e}"
223                ))
224            })?;
225
226        // Now that we have both bytes we can decode them into the era using
227        // the same logic as the codec::Decode impl does.
228        Era::decode(&mut &[first_byte, second_byte][..]).map_err(|e| {
229            scale_decode::Error::custom_string(format!(
230                "Failed to codec::Decode Era from Mortal bytes: {e}"
231            ))
232        })
233    }
234}