Skip to main content

miden_protocol/protocol_config/
next_protocol_config.rs

1use alloc::string::ToString;
2
3use super::ProtocolConfigError;
4use crate::block::BlockNumber;
5use crate::utils::serde::{
6    ByteReader,
7    ByteWriter,
8    Deserializable,
9    DeserializationError,
10    Serializable,
11};
12use crate::{Hasher, Word, ZERO};
13
14// NEXT PROTOCOL CONFIG
15// ================================================================================================
16
17/// A protocol upgrade that is scheduled but not yet in effect.
18///
19/// Committing to an upgrade ahead of time lets clients that come online before `effective_from`
20/// learn about it and update before the switch happens, instead of being blocked once it does.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct NextProtocolConfig {
23    /// The number of the first block for which the new configuration is in effect.
24    effective_from: BlockNumber,
25
26    /// The commitment to the [`ProtocolConfig`](super::ProtocolConfig) that becomes effective.
27    protocol_config: Word,
28}
29
30impl NextProtocolConfig {
31    // CONSTRUCTORS
32    // --------------------------------------------------------------------------------------------
33
34    /// Creates a new [`NextProtocolConfig`] from the provided inputs.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if `effective_from` is [`BlockNumber::GENESIS`]. The genesis block defines
39    /// the initial configuration, so no upgrade can become effective at it, and on-chain the
40    /// genesis block number is the encoding of "no upgrade scheduled".
41    pub fn new(
42        effective_from: BlockNumber,
43        protocol_config: Word,
44    ) -> Result<Self, ProtocolConfigError> {
45        if effective_from == BlockNumber::GENESIS {
46            return Err(ProtocolConfigError::NextConfigEffectiveAtGenesis);
47        }
48
49        Ok(Self { effective_from, protocol_config })
50    }
51
52    // PUBLIC ACCESSORS
53    // --------------------------------------------------------------------------------------------
54
55    /// Returns the number of the first block for which the new configuration is in effect.
56    pub fn effective_from(&self) -> BlockNumber {
57        self.effective_from
58    }
59
60    /// Returns the commitment to the [`ProtocolConfig`](super::ProtocolConfig) that becomes
61    /// effective.
62    pub fn protocol_config(&self) -> Word {
63        self.protocol_config
64    }
65
66    /// Returns a commitment to this scheduled upgrade.
67    ///
68    /// A block header without a scheduled upgrade commits to [`Word::empty`], which this commitment
69    /// can never collide with because `effective_from` is never zero.
70    pub fn to_commitment(&self) -> Word {
71        let effective_from = Word::new([self.effective_from.into(), ZERO, ZERO, ZERO]);
72        Hasher::merge(&[effective_from, self.protocol_config])
73    }
74}
75
76// SERIALIZATION
77// ================================================================================================
78
79impl Serializable for NextProtocolConfig {
80    fn write_into<W: ByteWriter>(&self, target: &mut W) {
81        let Self { effective_from, protocol_config } = self;
82
83        effective_from.write_into(target);
84        protocol_config.write_into(target);
85    }
86}
87
88impl Deserializable for NextProtocolConfig {
89    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
90        let effective_from = source.read()?;
91        let protocol_config = source.read()?;
92
93        Self::new(effective_from, protocol_config)
94            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
95    }
96}
97
98// TESTS
99// ================================================================================================
100
101#[cfg(test)]
102mod tests {
103    use assert_matches::assert_matches;
104    use miden_crypto::rand::test_utils::rand_value;
105
106    use super::*;
107
108    #[test]
109    fn commitment_binds_both_fields() {
110        let config = rand_value::<Word>();
111        let next = NextProtocolConfig::new(BlockNumber::from(10u32), config).unwrap();
112        let other_block = NextProtocolConfig::new(BlockNumber::from(11u32), config).unwrap();
113        let other_config =
114            NextProtocolConfig::new(BlockNumber::from(10u32), rand_value::<Word>()).unwrap();
115
116        assert_ne!(next.to_commitment(), other_block.to_commitment());
117        assert_ne!(next.to_commitment(), other_config.to_commitment());
118    }
119
120    #[test]
121    fn new_rejects_genesis() {
122        let error = NextProtocolConfig::new(BlockNumber::GENESIS, Word::empty()).unwrap_err();
123        assert_matches!(error, ProtocolConfigError::NextConfigEffectiveAtGenesis);
124    }
125
126    #[test]
127    fn serde_round_trip() -> anyhow::Result<()> {
128        let next = NextProtocolConfig::new(BlockNumber::from(42u32), rand_value::<Word>())?;
129
130        let deserialized = NextProtocolConfig::read_from_bytes(&next.to_bytes())
131            .map_err(|err| anyhow::anyhow!("{err}"))?;
132
133        assert_eq!(next, deserialized);
134        Ok(())
135    }
136}