Skip to main content

platform_version/version/
protocol_version.rs

1use crate::error::PlatformVersionError;
2use crate::version::dpp_versions::DPPVersion;
3use crate::version::drive_abci_versions::DriveAbciVersion;
4use crate::version::drive_versions::DriveVersion;
5use crate::version::fee::FeeVersion;
6#[cfg(feature = "mock-versions")]
7use crate::version::mocks::v2_test::TEST_PLATFORM_V2;
8#[cfg(feature = "mock-versions")]
9use crate::version::mocks::v3_test::TEST_PLATFORM_V3;
10#[cfg(feature = "mock-versions")]
11use crate::version::mocks::TEST_PROTOCOL_VERSION_SHIFT_BYTES;
12use crate::version::system_data_contract_versions::SystemDataContractVersions;
13#[cfg(feature = "mock-versions")]
14use std::sync::OnceLock;
15
16use crate::version::consensus_versions::ConsensusVersions;
17use crate::version::system_limits::SystemLimits;
18
19use crate::version::v1::PLATFORM_V1;
20use crate::version::v10::PLATFORM_V10;
21use crate::version::v11::PLATFORM_V11;
22use crate::version::v12::PLATFORM_V12;
23use crate::version::v2::PLATFORM_V2;
24use crate::version::v3::PLATFORM_V3;
25use crate::version::v4::PLATFORM_V4;
26use crate::version::v5::PLATFORM_V5;
27use crate::version::v6::PLATFORM_V6;
28use crate::version::v7::PLATFORM_V7;
29use crate::version::v8::PLATFORM_V8;
30use crate::version::v9::PLATFORM_V9;
31
32use crate::version::ProtocolVersion;
33pub use versioned_feature_core::*;
34
35#[derive(Clone, Debug)]
36pub struct PlatformVersion {
37    pub protocol_version: ProtocolVersion,
38    pub dpp: DPPVersion,
39    pub drive: DriveVersion,
40    pub drive_abci: DriveAbciVersion,
41    pub consensus: ConsensusVersions,
42    pub fee_version: FeeVersion,
43    pub system_data_contracts: SystemDataContractVersions,
44    pub system_limits: SystemLimits,
45}
46
47pub const PLATFORM_VERSIONS: &[PlatformVersion] = &[
48    PLATFORM_V1,
49    PLATFORM_V2,
50    PLATFORM_V3,
51    PLATFORM_V4,
52    PLATFORM_V5,
53    PLATFORM_V6,
54    PLATFORM_V7,
55    PLATFORM_V8,
56    PLATFORM_V9,
57    PLATFORM_V10,
58    PLATFORM_V11,
59    PLATFORM_V12,
60];
61
62#[cfg(feature = "mock-versions")]
63// We use OnceLock to be able to modify the version mocks
64pub static PLATFORM_TEST_VERSIONS: OnceLock<Vec<PlatformVersion>> = OnceLock::new();
65#[cfg(feature = "mock-versions")]
66const DEFAULT_PLATFORM_TEST_VERSIONS: &[PlatformVersion] = &[TEST_PLATFORM_V2, TEST_PLATFORM_V3];
67
68pub const LATEST_PLATFORM_VERSION: &PlatformVersion = &PLATFORM_V12;
69
70pub const DESIRED_PLATFORM_VERSION: &PlatformVersion = LATEST_PLATFORM_VERSION;
71
72impl PlatformVersion {
73    pub fn get<'a>(version: ProtocolVersion) -> Result<&'a Self, PlatformVersionError> {
74        if version > 0 {
75            #[cfg(feature = "mock-versions")]
76            {
77                if version >> TEST_PROTOCOL_VERSION_SHIFT_BYTES > 0 {
78                    let test_version = version - (1 << TEST_PROTOCOL_VERSION_SHIFT_BYTES);
79
80                    // Init default set of test versions
81                    let versions = PLATFORM_TEST_VERSIONS
82                        .get_or_init(|| vec![TEST_PLATFORM_V2, TEST_PLATFORM_V3]);
83
84                    return versions.get(test_version as usize - 2).ok_or(
85                        PlatformVersionError::UnknownVersionError(format!(
86                            "no test platform version {test_version}"
87                        )),
88                    );
89                }
90            }
91            PLATFORM_VERSIONS.get(version as usize - 1).ok_or_else(|| {
92                PlatformVersionError::UnknownVersionError(format!("no platform version {version}"))
93            })
94        } else {
95            Err(PlatformVersionError::UnknownVersionError(format!(
96                "no platform version {version}"
97            )))
98        }
99    }
100
101    pub fn get_optional<'a>(version: ProtocolVersion) -> Option<&'a Self> {
102        if version > 0 {
103            #[cfg(feature = "mock-versions")]
104            {
105                if version >> TEST_PROTOCOL_VERSION_SHIFT_BYTES > 0 {
106                    let test_version = version - (1 << TEST_PROTOCOL_VERSION_SHIFT_BYTES);
107
108                    // Init default set of test versions
109                    let versions = PLATFORM_TEST_VERSIONS
110                        .get_or_init(|| vec![TEST_PLATFORM_V2, TEST_PLATFORM_V3]);
111
112                    return versions.get(test_version as usize - 2);
113                }
114            }
115            PLATFORM_VERSIONS.get(version as usize - 1)
116        } else {
117            None
118        }
119    }
120
121    pub fn get_version_or_latest<'a>(
122        version: Option<ProtocolVersion>,
123    ) -> Result<&'a Self, PlatformVersionError> {
124        if let Some(version) = version {
125            if version > 0 {
126                #[cfg(feature = "mock-versions")]
127                {
128                    if version >> TEST_PROTOCOL_VERSION_SHIFT_BYTES > 0 {
129                        let test_version = version - (1 << TEST_PROTOCOL_VERSION_SHIFT_BYTES);
130
131                        // Init default set of test versions
132                        let versions = PLATFORM_TEST_VERSIONS
133                            .get_or_init(|| Vec::from(DEFAULT_PLATFORM_TEST_VERSIONS));
134
135                        return versions.get(test_version as usize - 2).ok_or(
136                            PlatformVersionError::UnknownVersionError(format!(
137                                "no test platform version {test_version}"
138                            )),
139                        );
140                    }
141                }
142                PLATFORM_VERSIONS.get(version as usize - 1).ok_or(
143                    PlatformVersionError::UnknownVersionError(format!(
144                        "no platform version {version}"
145                    )),
146                )
147            } else {
148                Err(PlatformVersionError::UnknownVersionError(format!(
149                    "no platform version {version}"
150                )))
151            }
152        } else {
153            Ok(Self::latest())
154        }
155    }
156
157    pub fn first<'a>() -> &'a Self {
158        PLATFORM_VERSIONS
159            .first()
160            .expect("expected to have a platform version")
161    }
162
163    pub fn latest<'a>() -> &'a Self {
164        PLATFORM_VERSIONS
165            .last()
166            .expect("expected to have a platform version")
167    }
168
169    pub fn desired<'a>() -> &'a Self {
170        DESIRED_PLATFORM_VERSION
171    }
172
173    #[cfg(feature = "mock-versions")]
174    /// Set mock versions for testing
175    pub fn replace_test_versions(versions: Vec<PlatformVersion>) {
176        PLATFORM_TEST_VERSIONS
177            .set(versions)
178            .expect("failed to set test versions")
179    }
180}
181
182#[cfg(test)]
183mod shielded_pool_gating_tests {
184    use super::PlatformVersion;
185
186    // The shielded credit pool lives in GroveDB at
187    // `[RootTree::ShieldedBalances (52), MAIN_SHIELDED_CREDIT_POOL_KEY ("M" = 0x4d)]`.
188    // That subtree is created ONLY at the protocol v12 upgrade migration
189    // (`transition_to_version_12`) or on a v12 genesis (`create_initial_state_structure_v3`).
190    // It does not exist on a protocol-v11 state (v11 uses init structure v2).
191    //
192    // The block-end / recent-block-storage shielded methods below read that
193    // subtree unconditionally every block. If they are active before v12 they
194    // open `[52, "M"]` on a state where it was never created, producing the
195    // consensus-breaking GroveDB error
196    //   "path parent layer not found: could not get key 4d for parent [52]".
197    //
198    // These methods were inactive (`None`) through protocol v11 in the released
199    // 3.0.1 line, then accidentally turned on for v11 when the four fields were
200    // added as `Some(0)` to the SHARED `DRIVE_ABCI_METHOD_VERSIONS_V7` struct in
201    // the Medusa shielded-pool PR (#3177) — V7 is referenced by both v11.rs and
202    // v12.rs. These tests pin the invariant: shielded reads are gated to v12+.
203
204    #[test]
205    fn shielded_block_processing_methods_inactive_before_v12() {
206        let v11 = PlatformVersion::get(11).expect("protocol version 11 must exist");
207        let block_end = &v11.drive_abci.methods.block_end;
208        let st = &v11.drive_abci.methods.state_transition_processing;
209
210        assert_eq!(
211            block_end.record_shielded_pool_anchor, None,
212            "v11 must NOT record shielded pool anchors: the [52, \"M\"] subtree does not exist before v12"
213        );
214        assert_eq!(
215            block_end.prune_shielded_pool_anchors, None,
216            "v11 must NOT prune shielded pool anchors: the [52, \"M\"] subtree does not exist before v12"
217        );
218        assert_eq!(
219            st.store_nullifiers_to_recent_block_storage, None,
220            "v11 must NOT store shielded nullifiers: the [52, \"M\"] subtree does not exist before v12"
221        );
222        assert_eq!(
223            st.cleanup_recent_block_storage_nullifiers, None,
224            "v11 must NOT clean up shielded nullifiers: the [52, \"M\"] subtree does not exist before v12"
225        );
226    }
227
228    #[test]
229    fn shielded_block_processing_methods_active_at_v12() {
230        let v12 = PlatformVersion::get(12).expect("protocol version 12 must exist");
231        let block_end = &v12.drive_abci.methods.block_end;
232        let st = &v12.drive_abci.methods.state_transition_processing;
233
234        // At v12 the shielded pool subtree is created by the upgrade migration,
235        // so the same methods must be active.
236        assert_eq!(block_end.record_shielded_pool_anchor, Some(0));
237        assert_eq!(block_end.prune_shielded_pool_anchors, Some(0));
238        assert_eq!(st.store_nullifiers_to_recent_block_storage, Some(0));
239        assert_eq!(st.cleanup_recent_block_storage_nullifiers, Some(0));
240    }
241}