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