1use crate::error::Error;
2use arc_swap::ArcSwap;
3use dpp::data_contract::DataContract;
4use dpp::prelude::Identifier;
5use dpp::system_data_contracts::{load_system_data_contract, SystemDataContract};
6use platform_version::version::feature_initial_protocol_versions::{
7 APP_CONNECT_CONTRACT_INITIAL_PROTOCOL_VERSION,
8 MODERATION_CHARTERS_CONTRACT_INITIAL_PROTOCOL_VERSION,
9};
10use platform_version::version::{PlatformVersion, ProtocolVersion};
11use std::collections::BTreeMap;
12use std::sync::Arc;
13
14const MAX_MEMOIZED_PROTOCOL_VERSIONS: usize = 2;
22
23type Materializations = BTreeMap<(ProtocolVersion, SystemDataContract), Arc<DataContract>>;
26
27pub struct SystemDataContracts {
49 materialized: ArcSwap<Materializations>,
50}
51
52impl Default for SystemDataContracts {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl SystemDataContracts {
59 pub fn new() -> Self {
61 SystemDataContracts {
62 materialized: ArcSwap::from_pointee(Materializations::new()),
63 }
64 }
65
66 pub fn load(
79 &self,
80 system_contract: SystemDataContract,
81 platform_version: &PlatformVersion,
82 ) -> Result<Arc<DataContract>, Error> {
83 let key = (platform_version.protocol_version, system_contract);
84
85 let materialized = self.materialized.load();
86 if let Some(contract) = materialized.get(&key) {
87 return Ok(Arc::clone(contract));
88 }
89 drop(materialized);
90
91 let contract = Arc::new(load_system_data_contract(
92 system_contract,
93 platform_version,
94 )?);
95
96 self.materialized.rcu(|materialized| {
101 let mut next = Materializations::clone(materialized);
102 next.insert(key, Arc::clone(&contract));
103 Self::drop_stale_protocol_versions(&mut next);
104 next
105 });
106
107 Ok(contract)
108 }
109
110 pub fn load_withdrawals(
112 &self,
113 platform_version: &PlatformVersion,
114 ) -> Result<Arc<DataContract>, Error> {
115 self.load(SystemDataContract::Withdrawals, platform_version)
116 }
117
118 pub fn load_token_history(
120 &self,
121 platform_version: &PlatformVersion,
122 ) -> Result<Arc<DataContract>, Error> {
123 self.load(SystemDataContract::TokenHistory, platform_version)
124 }
125
126 pub fn load_dpns(
128 &self,
129 platform_version: &PlatformVersion,
130 ) -> Result<Arc<DataContract>, Error> {
131 self.load(SystemDataContract::DPNS, platform_version)
132 }
133
134 pub fn load_dashpay(
136 &self,
137 platform_version: &PlatformVersion,
138 ) -> Result<Arc<DataContract>, Error> {
139 self.load(SystemDataContract::Dashpay, platform_version)
140 }
141
142 pub fn load_masternode_reward_shares(
144 &self,
145 platform_version: &PlatformVersion,
146 ) -> Result<Arc<DataContract>, Error> {
147 self.load(SystemDataContract::MasternodeRewards, platform_version)
148 }
149
150 pub fn load_keyword_search(
152 &self,
153 platform_version: &PlatformVersion,
154 ) -> Result<Arc<DataContract>, Error> {
155 self.load(SystemDataContract::KeywordSearch, platform_version)
156 }
157
158 pub fn load_document_history(
160 &self,
161 platform_version: &PlatformVersion,
162 ) -> Result<Arc<DataContract>, Error> {
163 self.load(SystemDataContract::DocumentHistory, platform_version)
164 }
165
166 pub fn load_app_connect(
168 &self,
169 platform_version: &PlatformVersion,
170 ) -> Result<Arc<DataContract>, Error> {
171 self.load(SystemDataContract::AppConnect, platform_version)
172 }
173
174 pub fn load_moderation_charters(
176 &self,
177 platform_version: &PlatformVersion,
178 ) -> Result<Arc<DataContract>, Error> {
179 self.load(SystemDataContract::ModerationCharters, platform_version)
180 }
181
182 pub fn find_by_id(
191 &self,
192 id: Identifier,
193 platform_version: &PlatformVersion,
194 ) -> Result<Option<Arc<DataContract>>, Error> {
195 let Some(&system_contract) = SystemDataContract::ALL
198 .iter()
199 .find(|system_contract| system_contract.id() == id)
200 else {
201 return Ok(None);
202 };
203
204 let activated_at_protocol_version: ProtocolVersion = match system_contract {
211 SystemDataContract::Withdrawals
213 | SystemDataContract::MasternodeRewards
214 | SystemDataContract::DPNS
215 | SystemDataContract::Dashpay => 1,
216 SystemDataContract::TokenHistory | SystemDataContract::KeywordSearch => 9,
218 SystemDataContract::DocumentHistory => 13,
220 SystemDataContract::AppConnect => APP_CONNECT_CONTRACT_INITIAL_PROTOCOL_VERSION,
222 SystemDataContract::ModerationCharters => {
224 MODERATION_CHARTERS_CONTRACT_INITIAL_PROTOCOL_VERSION
225 }
226 SystemDataContract::WalletUtils | SystemDataContract::FeatureFlags => return Ok(None),
229 };
230
231 if activated_at_protocol_version > platform_version.protocol_version {
232 return Ok(None);
233 }
234
235 self.load(system_contract, platform_version).map(Some)
236 }
237
238 pub fn drop_versions_below(&self, protocol_version: ProtocolVersion) {
249 if self
252 .materialized
253 .load()
254 .keys()
255 .all(|(memoized, _)| *memoized >= protocol_version)
256 {
257 return;
258 }
259
260 self.materialized.rcu(|materialized| {
261 let mut next = Materializations::clone(materialized);
262 next.retain(|(memoized, _), _| *memoized >= protocol_version);
263 next
264 });
265 }
266
267 fn drop_stale_protocol_versions(materialized: &mut Materializations) {
273 let mut protocol_versions: Vec<ProtocolVersion> = materialized
276 .keys()
277 .map(|(protocol_version, _)| *protocol_version)
278 .collect();
279 protocol_versions.dedup();
280
281 let Some(lowest_kept) = protocol_versions
282 .len()
283 .checked_sub(MAX_MEMOIZED_PROTOCOL_VERSIONS)
284 .and_then(|first_kept| protocol_versions.get(first_kept))
285 .copied()
286 else {
287 return;
288 };
289
290 materialized.retain(|(protocol_version, _), _| *protocol_version >= lowest_kept);
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use dpp::data_contract::accessors::v0::DataContractV0Getters;
298 use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
299
300 fn platform_version(protocol_version: ProtocolVersion) -> &'static PlatformVersion {
301 PlatformVersion::get(protocol_version).expect("expected a supported platform version")
302 }
303
304 fn dpns_history_flags(contract: &DataContract) -> (bool, bool, bool) {
305 let domain = contract
306 .document_type_for_name("domain")
307 .expect("DPNS must define the domain document type");
308
309 (
310 domain.documents_keep_transfer_history(),
311 domain.documents_keep_purchase_history(),
312 domain.documents_keep_pricing_history(),
313 )
314 }
315
316 #[test]
317 fn reloading_v13_must_not_make_dpns_v2_visible_to_v12_reads() {
318 let contracts = SystemDataContracts::new();
319
320 assert_eq!(
321 dpns_history_flags(
322 &contracts
323 .find_by_id(SystemDataContract::DPNS.id(), platform_version(12))
324 .expect("expected the v12 DPNS lookup to succeed")
325 .expect("DPNS must be active at protocol v12")
326 ),
327 (false, false, false)
328 );
329
330 contracts
333 .load_dpns(platform_version(13))
334 .expect("speculatively materialize protocol v13 DPNS");
335
336 assert_eq!(
337 dpns_history_flags(
338 &contracts
339 .find_by_id(SystemDataContract::DPNS.id(), platform_version(12))
340 .expect("expected the v12 DPNS lookup to succeed")
341 .expect("DPNS must remain available at protocol v12")
342 ),
343 (false, false, false),
344 "a speculative v13 materialization must preserve explicit v12 reads"
345 );
346 assert_eq!(
347 dpns_history_flags(
348 &contracts
349 .load_dpns(platform_version(12))
350 .expect("expected the v12 DPNS accessor to succeed")
351 ),
352 (false, false, false),
353 "a speculative v13 materialization must preserve v12 accessor reads"
354 );
355 assert_eq!(
356 dpns_history_flags(
357 &contracts
358 .find_by_id(SystemDataContract::DPNS.id(), platform_version(13))
359 .expect("expected the v13 DPNS lookup to succeed")
360 .expect("DPNS must be active at protocol v13")
361 ),
362 (true, true, true)
363 );
364 }
365
366 #[test]
367 fn same_feature_version_must_preserve_distinct_protocol_materializations() {
368 let contracts = SystemDataContracts::new();
369 let platform_version_8 = platform_version(8);
370 let platform_version_9 = platform_version(9);
371 let expected_v8 =
372 load_system_data_contract(SystemDataContract::Withdrawals, platform_version_8)
373 .expect("load protocol v8 withdrawals");
374 let expected_v9 =
375 load_system_data_contract(SystemDataContract::Withdrawals, platform_version_9)
376 .expect("load protocol v9 withdrawals");
377 assert_ne!(
378 expected_v8, expected_v9,
379 "the regression requires distinct materialized contracts"
380 );
381
382 contracts
383 .load_withdrawals(platform_version_8)
384 .expect("materialize protocol v8 withdrawals");
385 contracts
386 .load_withdrawals(platform_version_9)
387 .expect("speculatively materialize protocol v9 withdrawals");
388
389 assert_eq!(
390 contracts
391 .find_by_id(SystemDataContract::Withdrawals.id(), platform_version_8)
392 .expect("expected the v8 withdrawals lookup to succeed")
393 .expect("withdrawals must be active at protocol v8")
394 .as_ref(),
395 &expected_v8,
396 "a v9 materialization must preserve the protocol v8 one"
397 );
398 assert_eq!(
399 contracts
400 .find_by_id(SystemDataContract::Withdrawals.id(), platform_version_9)
401 .expect("expected the v9 withdrawals lookup to succeed")
402 .expect("withdrawals must be active at protocol v9")
403 .as_ref(),
404 &expected_v9
405 );
406 }
407
408 #[test]
409 fn document_history_cache_respects_its_activation_version() {
410 let contracts = SystemDataContracts::new();
411
412 assert!(contracts
413 .find_by_id(
414 SystemDataContract::DocumentHistory.id(),
415 platform_version(12)
416 )
417 .expect("expected the pre-activation lookup to succeed")
418 .is_none());
419 assert!(contracts
420 .find_by_id(
421 SystemDataContract::DocumentHistory.id(),
422 platform_version(13)
423 )
424 .expect("expected the v13 lookup to succeed")
425 .is_some());
426 }
427
428 #[test]
429 fn should_serve_moderation_charters_only_from_its_activation_version() {
430 let contracts = SystemDataContracts::new();
431
432 assert!(contracts
433 .find_by_id(
434 SystemDataContract::ModerationCharters.id(),
435 platform_version(13)
436 )
437 .expect("expected the pre-activation lookup to succeed")
438 .is_none());
439 assert!(contracts
440 .find_by_id(
441 SystemDataContract::ModerationCharters.id(),
442 PlatformVersion::latest()
443 )
444 .expect("expected the v14 lookup to succeed")
445 .is_some());
446 assert!(contracts
447 .find_by_id(
448 SystemDataContract::ModerationCharters.id(),
449 platform_version(13)
450 )
451 .expect("an old-version lookup after materialization must succeed")
452 .is_none());
453 }
454
455 #[test]
456 fn should_serve_app_connect_only_from_its_activation_version() {
457 let contracts = SystemDataContracts::new();
458
459 assert!(contracts
460 .find_by_id(SystemDataContract::AppConnect.id(), platform_version(13))
461 .expect("expected the pre-activation lookup to succeed")
462 .is_none());
463 assert!(contracts
464 .find_by_id(
465 SystemDataContract::AppConnect.id(),
466 PlatformVersion::latest()
467 )
468 .expect("expected the v14 lookup to succeed")
469 .is_some());
470 assert!(contracts
471 .find_by_id(SystemDataContract::AppConnect.id(), platform_version(13))
472 .expect("an old-version lookup after materialization must succeed")
473 .is_none());
474 }
475
476 fn memoized_protocol_versions(contracts: &SystemDataContracts) -> Vec<ProtocolVersion> {
477 let materialized = contracts.materialized.load();
478 let mut protocol_versions: Vec<ProtocolVersion> = materialized
479 .keys()
480 .map(|(protocol_version, _)| *protocol_version)
481 .collect();
482 protocol_versions.dedup();
483 protocol_versions
484 }
485
486 #[test]
489 fn committing_a_block_releases_the_outgoing_protocol_version() {
490 let contracts = SystemDataContracts::new();
491
492 for committed_protocol_version in 9..=14 {
493 contracts
496 .load_dpns(platform_version(committed_protocol_version))
497 .expect("materialize DPNS for the block being executed");
498 assert!(
499 memoized_protocol_versions(&contracts).len() <= 2,
500 "at most the committed and candidate versions may be live"
501 );
502
503 contracts.drop_versions_below(committed_protocol_version);
504 assert_eq!(
505 memoized_protocol_versions(&contracts),
506 vec![committed_protocol_version],
507 "committing the switch must release the outgoing version"
508 );
509 }
510 }
511
512 #[test]
514 fn memoization_is_bounded_to_the_live_protocol_versions() {
515 let contracts = SystemDataContracts::new();
516
517 for protocol_version in [9, 10, 11, 12, 13, 14] {
518 contracts
519 .load_dpns(platform_version(protocol_version))
520 .expect("materialize DPNS");
521 }
522
523 assert_eq!(
524 memoized_protocol_versions(&contracts),
525 vec![13, 14],
526 "only the most recent protocol versions stay memoized"
527 );
528
529 assert_eq!(
531 dpns_history_flags(
532 &contracts
533 .load_dpns(platform_version(9))
534 .expect("re-materialize protocol v9 DPNS")
535 ),
536 (false, false, false)
537 );
538 }
539}