Skip to main content

drive/cache/
system_contracts.rs

1use crate::error::Error;
2use arc_swap::{ArcSwap, Guard};
3use dpp::data_contract::DataContract;
4use dpp::prelude::Identifier;
5use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract};
6use platform_version::version::{PlatformVersion, ProtocolVersion};
7use std::sync::Arc;
8
9/// A wrapper around a system [`DataContract`] that tracks its activation version
10/// and allows atomic replacement.
11///
12/// This is used for system data contracts that may be updated over time while
13/// tracking the protocol version from which they are considered active.
14pub struct ActiveSystemDataContract {
15    /// The current active version of the data contract.
16    pub contract: ArcSwap<DataContract>,
17
18    /// The protocol version since which this contract is considered active.
19    #[allow(unused)]
20    pub active_since_protocol_version: ProtocolVersion,
21}
22
23impl ActiveSystemDataContract {
24    /// Atomically replaces the current data contract with a new one.
25    ///
26    /// # Arguments
27    ///
28    /// * `contract` - The new [`DataContract`] to store.
29    pub fn store(&self, contract: DataContract) {
30        self.contract.store(Arc::new(contract));
31    }
32
33    /// Loads the current data contract.
34    ///
35    /// Returns a guard that provides shared access to the current [`DataContract`].
36    /// The guard keeps the contract alive for the duration of the borrow.
37    pub fn load(&self) -> Guard<Arc<DataContract>> {
38        self.contract.load()
39    }
40
41    /// Creates a new [`ActiveSystemDataContract`] with the given contract and activation version.
42    ///
43    /// # Arguments
44    ///
45    /// * `contract` - The initial [`DataContract`] to store.
46    /// * `active_since_protocol_version` - The protocol version from which this contract is considered active.
47    pub fn new(contract: DataContract, active_since_protocol_version: ProtocolVersion) -> Self {
48        ActiveSystemDataContract {
49            contract: ArcSwap::from_pointee(contract),
50            active_since_protocol_version,
51        }
52    }
53}
54
55/// System contracts
56pub struct SystemDataContracts {
57    /// Withdrawal contract
58    withdrawals: ActiveSystemDataContract,
59    /// DPNS contract
60    dpns: ActiveSystemDataContract,
61    /// Dashpay contract
62    dashpay: ActiveSystemDataContract,
63    /// Masternode reward shares contract
64    masternode_reward_shares: ActiveSystemDataContract,
65    /// Token history contract
66    token_history: ActiveSystemDataContract,
67    /// Search contract
68    keyword_search: ActiveSystemDataContract,
69}
70
71impl SystemDataContracts {
72    /// Reload **all** core-protocol system contracts for the supplied platform version,
73    /// atomically replacing the cached copies held in each `ArcSwap`.
74    ///
75    /// Call this after you upgrade `PlatformVersion` (e.g. when a protocol bump
76    /// introduces new schemas for DPNS, Token History, etc.).
77    ///
78    /// # Errors
79    /// Propagates any `Error` returned by `load_system_data_contract`.
80    pub fn reload_system_contracts(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
81        use SystemDataContract::*;
82
83        // 1. Load every contract fresh (fail fast on error).
84        let withdrawals = load_system_data_contract(Withdrawals, platform_version)?;
85        let dpns = load_system_data_contract(DPNS, platform_version)?;
86        let dashpay = load_system_data_contract(Dashpay, platform_version)?;
87        let masternode_reward_shares =
88            load_system_data_contract(MasternodeRewards, platform_version)?;
89        let token_history = load_system_data_contract(TokenHistory, platform_version)?;
90        let keyword_search = load_system_data_contract(KeywordSearch, platform_version)?;
91
92        // 2. Swap the cached Arcs — each swap is lock-free & O(1).
93        self.withdrawals.store(withdrawals);
94        self.dpns.store(dpns);
95        self.dashpay.store(dashpay);
96        self.masternode_reward_shares
97            .store(masternode_reward_shares);
98        self.token_history.store(token_history);
99        self.keyword_search.store(keyword_search);
100
101        Ok(())
102    }
103
104    /// load genesis system contracts
105    pub fn load_genesis_system_contracts() -> Result<Self, Error> {
106        // We should use the version where the contract became active for each data contract
107        Ok(Self {
108            withdrawals: ActiveSystemDataContract::new(
109                load_system_data_contract(
110                    SystemDataContract::Withdrawals,
111                    PlatformVersion::first(),
112                )?,
113                1,
114            ),
115            dpns: ActiveSystemDataContract::new(
116                load_system_data_contract(SystemDataContract::DPNS, PlatformVersion::first())?,
117                1,
118            ),
119            dashpay: ActiveSystemDataContract::new(
120                load_system_data_contract(SystemDataContract::Dashpay, PlatformVersion::first())?,
121                1,
122            ),
123            masternode_reward_shares: ActiveSystemDataContract::new(
124                load_system_data_contract(
125                    SystemDataContract::MasternodeRewards,
126                    PlatformVersion::first(),
127                )?,
128                1,
129            ),
130            token_history: ActiveSystemDataContract::new(
131                load_system_data_contract(
132                    SystemDataContract::TokenHistory,
133                    PlatformVersion::first(),
134                )?,
135                9,
136            ),
137            keyword_search: ActiveSystemDataContract::new(
138                load_system_data_contract(
139                    SystemDataContract::KeywordSearch,
140                    PlatformVersion::first(),
141                )?,
142                9,
143            ),
144        })
145    }
146
147    /// Returns withdrawals contract
148    pub fn load_withdrawals(&self) -> Guard<Arc<DataContract>> {
149        self.withdrawals.load()
150    }
151
152    /// Returns token history contract
153    pub fn load_token_history(&self) -> Guard<Arc<DataContract>> {
154        self.token_history.load()
155    }
156
157    /// Returns DPNS contract
158    pub fn load_dpns(&self) -> Guard<Arc<DataContract>> {
159        self.dpns.load()
160    }
161
162    /// Returns Dashpay contract
163    pub fn load_dashpay(&self) -> Guard<Arc<DataContract>> {
164        self.dashpay.load()
165    }
166
167    /// Returns Masternode reward shares contract
168    pub fn load_masternode_reward_shares(&self) -> Guard<Arc<DataContract>> {
169        self.masternode_reward_shares.load()
170    }
171
172    /// Returns the search contract
173    pub fn load_keyword_search(&self) -> Guard<Arc<DataContract>> {
174        self.keyword_search.load()
175    }
176
177    /// Returns the cached system contract whose deterministic identifier matches `id`,
178    /// if any. Returns `None` for user contracts and for any system contract whose
179    /// definition isn't held in this in-memory cache (e.g. `WalletUtils`, which lives
180    /// only in grovedb).
181    pub fn find_by_id(&self, id: Identifier) -> Option<Arc<DataContract>> {
182        // Compare against each cached system contract's static `id_bytes`. The match
183        // is `O(n)` over a small fixed set of variants — cheaper than building a map.
184        let active = if id == SystemDataContract::Withdrawals.id() {
185            &self.withdrawals
186        } else if id == SystemDataContract::MasternodeRewards.id() {
187            &self.masternode_reward_shares
188        } else if id == SystemDataContract::DPNS.id() {
189            &self.dpns
190        } else if id == SystemDataContract::Dashpay.id() {
191            &self.dashpay
192        } else if id == SystemDataContract::TokenHistory.id() {
193            &self.token_history
194        } else if id == SystemDataContract::KeywordSearch.id() {
195            &self.keyword_search
196        } else {
197            return None;
198        };
199        Some(Arc::clone(&active.load()))
200    }
201}