namada_core/
parameters.rs

1//! Protocol parameters types
2
3use std::collections::BTreeMap;
4use std::fmt;
5use std::io::{self, Read};
6use std::num::NonZeroU64;
7
8use namada_macros::BorshDeserializer;
9#[cfg(feature = "migrations")]
10use namada_migrations::*;
11use serde::{Deserialize, Serialize};
12
13use super::address::Address;
14use super::hash::Hash;
15use super::time::DurationSecs;
16use super::token;
17use crate::borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
18
19/// Protocol parameters
20#[derive(
21    Clone,
22    Debug,
23    PartialEq,
24    Eq,
25    PartialOrd,
26    Ord,
27    Hash,
28    BorshSerialize,
29    BorshDeserialize,
30    BorshDeserializer,
31    BorshSchema,
32)]
33pub struct Parameters {
34    /// Max payload size, in bytes, for a mempool tx.
35    pub max_tx_bytes: u32,
36    /// Epoch duration (read only)
37    pub epoch_duration: EpochDuration,
38    /// Max payload size, in bytes, for a tx batch proposal.
39    pub max_proposal_bytes: ProposalBytes,
40    /// Max gas for block
41    pub max_block_gas: u64,
42    /// Allowed validity predicate hashes (read only)
43    pub vp_allowlist: Vec<String>,
44    /// Allowed tx hashes (read only)
45    pub tx_allowlist: Vec<String>,
46    /// Implicit accounts validity predicate WASM code hash
47    pub implicit_vp_code_hash: Option<Hash>,
48    /// Expected number of epochs per year (read only)
49    pub epochs_per_year: u64,
50    /// The multiplier for masp epochs (it requires this amount of epochs to
51    /// transition to the next masp epoch)
52    pub masp_epoch_multiplier: u64,
53    /// The gas limit for a masp transaction paying fees
54    pub masp_fee_payment_gas_limit: u64,
55    /// Gas scale
56    pub gas_scale: u64,
57    /// Map of the cost per gas unit for every token allowed for fee payment
58    pub minimum_gas_price: BTreeMap<Address, token::Amount>,
59    /// Enable the native token transfer if it is true
60    pub is_native_token_transferable: bool,
61}
62
63/// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks`
64/// and `min_duration` have passed since the beginning of the current epoch.
65#[derive(
66    Clone,
67    Debug,
68    PartialEq,
69    Eq,
70    PartialOrd,
71    Ord,
72    Hash,
73    BorshSerialize,
74    BorshDeserialize,
75    BorshDeserializer,
76    BorshSchema,
77)]
78pub struct EpochDuration {
79    /// Minimum number of blocks in an epoch
80    pub min_num_of_blocks: u64,
81    /// Minimum duration of an epoch
82    pub min_duration: DurationSecs,
83}
84
85impl Default for Parameters {
86    fn default() -> Self {
87        Parameters {
88            max_tx_bytes: 1024 * 1024,
89            epoch_duration: EpochDuration {
90                min_num_of_blocks: 1,
91                min_duration: DurationSecs(3600),
92            },
93            max_proposal_bytes: Default::default(),
94            max_block_gas: 100,
95            vp_allowlist: vec![],
96            tx_allowlist: vec![],
97            implicit_vp_code_hash: Default::default(),
98            epochs_per_year: 365,
99            masp_epoch_multiplier: 2,
100            masp_fee_payment_gas_limit: 0,
101            gas_scale: 100_000_000,
102            minimum_gas_price: Default::default(),
103            is_native_token_transferable: true,
104        }
105    }
106}
107
108/// Configuration parameter for the upper limit on the number
109/// of bytes transactions can occupy in a block proposal.
110#[derive(
111    Copy,
112    Clone,
113    Eq,
114    PartialEq,
115    Ord,
116    PartialOrd,
117    Hash,
118    Debug,
119    BorshSerialize,
120    BorshDeserializer,
121)]
122#[repr(transparent)]
123pub struct ProposalBytes {
124    inner: NonZeroU64,
125}
126
127impl BorshDeserialize for ProposalBytes {
128    fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
129        let value: u64 = BorshDeserialize::deserialize_reader(reader)?;
130        Self::new(value).ok_or_else(|| {
131            io::Error::new(
132                io::ErrorKind::InvalidInput,
133                format!(
134                    "ProposalBytes value must be in the range 1 - {}",
135                    Self::RAW_MAX.get()
136                ),
137            )
138        })
139    }
140}
141
142impl Serialize for ProposalBytes {
143    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
144    where
145        S: serde::Serializer,
146    {
147        s.serialize_u64(self.inner.get())
148    }
149}
150
151impl<'de> Deserialize<'de> for ProposalBytes {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: serde::Deserializer<'de>,
155    {
156        struct Visitor;
157
158        impl serde::de::Visitor<'_> for Visitor {
159            type Value = ProposalBytes;
160
161            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162                write!(
163                    f,
164                    "a u64 in the range 1 - {}",
165                    ProposalBytes::RAW_MAX.get()
166                )
167            }
168
169            fn visit_u64<E>(self, size: u64) -> Result<Self::Value, E>
170            where
171                E: serde::de::Error,
172            {
173                ProposalBytes::new(size).ok_or_else(|| {
174                    serde::de::Error::invalid_value(
175                        serde::de::Unexpected::Unsigned(size),
176                        &self,
177                    )
178                })
179            }
180
181            // NOTE: this is only needed because of a bug in the toml parser
182            // - https://github.com/toml-rs/toml-rs/issues/256
183            // - https://github.com/toml-rs/toml/issues/512
184            //
185            // TODO(namada#3243): switch to `toml_edit` for TOML parsing
186            fn visit_i64<E>(self, size: i64) -> Result<Self::Value, E>
187            where
188                E: serde::de::Error,
189            {
190                let max_bytes = u64::try_from(size).map_err(|_e| {
191                    serde::de::Error::invalid_value(
192                        serde::de::Unexpected::Signed(size),
193                        &self,
194                    )
195                })?;
196                ProposalBytes::new(max_bytes).ok_or_else(|| {
197                    serde::de::Error::invalid_value(
198                        serde::de::Unexpected::Signed(size),
199                        &self,
200                    )
201                })
202            }
203        }
204
205        deserializer.deserialize_u64(Visitor)
206    }
207}
208
209impl BorshSchema for ProposalBytes {
210    fn add_definitions_recursively(
211        definitions: &mut std::collections::BTreeMap<
212            borsh::schema::Declaration,
213            borsh::schema::Definition,
214        >,
215    ) {
216        let fields = borsh::schema::Fields::NamedFields(vec![(
217            "inner".into(),
218            u64::declaration(),
219        )]);
220        let definition = borsh::schema::Definition::Struct { fields };
221        definitions.insert(Self::declaration(), definition);
222    }
223
224    fn declaration() -> borsh::schema::Declaration {
225        std::any::type_name::<Self>().into()
226    }
227}
228
229impl Default for ProposalBytes {
230    #[inline]
231    fn default() -> Self {
232        Self {
233            inner: Self::RAW_DEFAULT,
234        }
235    }
236}
237
238// constants
239impl ProposalBytes {
240    /// The upper bound of a [`ProposalBytes`] value.
241    pub const MAX: ProposalBytes = ProposalBytes {
242        inner: Self::RAW_MAX,
243    };
244    /// The (raw) default value for a [`ProposalBytes`].
245    ///
246    /// This value must be within the range `[1 B, RAW_MAX MiB]`.
247    const RAW_DEFAULT: NonZeroU64 = Self::RAW_MAX;
248    /// The (raw) upper bound of a [`ProposalBytes`] value.
249    ///
250    /// The maximum space a serialized Tendermint block can
251    /// occupy is 100 MiB. We reserve 10 MiB for serialization
252    /// overhead, evidence and header data. For P2P safety
253    /// reasons (i.e. DoS protection) we hardcap the size of
254    /// tx data to 6 MiB.
255    const RAW_MAX: NonZeroU64 = NonZeroU64::new(6 * 1024 * 1024).unwrap();
256}
257
258impl ProposalBytes {
259    /// Return the number of bytes as a [`u64`] value.
260    #[inline]
261    pub const fn get(self) -> u64 {
262        self.inner.get()
263    }
264
265    /// Try to construct a new [`ProposalBytes`] instance,
266    /// from the given `max_bytes` value.
267    ///
268    /// This function will return [`None`] if `max_bytes` is not within
269    /// the inclusive range of 1 to [`ProposalBytes::MAX`].
270    #[inline]
271    pub fn new(max_bytes: u64) -> Option<Self> {
272        NonZeroU64::new(max_bytes)
273            .map(|inner| Self { inner })
274            .and_then(|value| {
275                if value.get() > Self::RAW_MAX.get() {
276                    None
277                } else {
278                    Some(value)
279                }
280            })
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use proptest::prelude::*;
287
288    use super::*;
289
290    proptest! {
291        /// Test if [`ProposalBytes`] serde serialization is correct.
292        #[test]
293        fn test_proposal_size_serialize_roundtrip(s in 1u64..=ProposalBytes::MAX.get()) {
294            let size = ProposalBytes::new(s).expect("Test failed");
295            assert_eq!(size.get(), s);
296            let json = serde_json::to_string(&size).expect("Test failed");
297            let deserialized: ProposalBytes =
298                serde_json::from_str(&json).expect("Test failed");
299            assert_eq!(size, deserialized);
300        }
301    }
302}