diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs index 79d2034f99c..b0b9fe0fa66 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/should_checkpoint/v0/mod.rs @@ -203,7 +203,17 @@ mod tests { return; } + // Checkpoints are off in the default test config, and the guard for + // that returns before the age of the block is looked at. They have to + // be on for this test to reach the code it is about. let platform = TestPlatformBuilder::new() + .with_config(crate::config::PlatformConfig { + testing_configs: crate::config::PlatformTestConfig { + disable_checkpoints: false, + ..Default::default() + }, + ..Default::default() + }) .build_with_mock_rpc() .set_genesis_state(); diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/update_state_cache/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/update_state_cache/v0/mod.rs index 8e3b8d0e285..2fa6f272113 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/update_state_cache/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/update_state_cache/v0/mod.rs @@ -54,6 +54,11 @@ where // Persist block state self.store_platform_state(&block_platform_state, Some(transaction), platform_version)?; + // Whatever the store wrote is now what is on disk for this block, so the + // next block only has to write the full record if it changes something + // heavy itself. + block_platform_state.heavy_fields_dirty = false; + let block_platform_state = Arc::new(block_platform_state); self.state.store(block_platform_state); @@ -222,4 +227,129 @@ mod tests { "genesis_block_info must always be cleared" ); } + + /// While replaying history the full saved record is rewritten only when a + /// heavy field changed; the small record carries the block info in between. + /// A node restarted from disk must see the newest block info and the heavy + /// fields from the last full write. + #[test] + fn v0_historical_block_with_clean_heavy_fields_reloads_from_the_small_record() { + use crate::config::{PlatformConfig, PlatformTestConfig}; + use crate::platform_types::platform::Platform; + use crate::platform_types::platform_state::PlatformState; + use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; + use dpp::dashcore::{ProTxHash, Txid}; + use dpp::dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType}; + use dpp::serialization::PlatformDeserializableFromVersionedStructure; + + let platform_version = PlatformVersion::latest(); + let platform = TestPlatformBuilder::new() + .with_config(PlatformConfig { + testing_configs: PlatformTestConfig { + store_platform_state: true, + ..PlatformTestConfig::default_minimal_verifications() + }, + ..Default::default() + }) + .build_with_mock_rpc() + .set_genesis_state(); + + let loaded = platform.state.load(); + let mut block_platform_state = loaded.as_ref().clone(); + drop(loaded); + + // A block from long ago, so the store treats it as replayed history. + let mut old_block = make_extended_block_info(7); + old_block.basic_info_mut().time_ms = 1_000_000; + + // Block 7 changes a heavy field, so it is written in full. + let pro_tx_hash = ProTxHash::from_byte_array([0x77u8; 32]); + let masternode = MasternodeListItem { + node_type: MasternodeType::Regular, + pro_tx_hash, + collateral_hash: Txid::from_byte_array([0u8; 32]), + collateral_index: 0, + collateral_address: [0u8; 20], + operator_reward: 0.0, + state: DMNState { + service: "1.2.3.4:1234".parse().expect("socket address"), + registered_height: 0, + pose_revived_height: None, + pose_ban_height: None, + revocation_reason: 0, + owner_address: [0u8; 20], + voting_address: [0u8; 20], + payout_address: [0u8; 20], + pub_key_operator: vec![0u8; 48], + operator_payout_address: None, + platform_node_id: None, + platform_p2p_port: None, + platform_http_port: None, + }, + }; + block_platform_state + .full_masternode_list_mut() + .insert(pro_tx_hash, masternode); + assert!(block_platform_state.heavy_fields_dirty); + + let transaction = platform.drive.grove.start_transaction(); + platform + .update_state_cache_v0( + old_block, + block_platform_state, + &transaction, + platform_version, + ) + .expect("block 7 must be stored"); + + // Block 8 changes nothing heavy, so only the small record is written. + let loaded = platform.state.load(); + let block_platform_state = loaded.as_ref().clone(); + drop(loaded); + assert!(!block_platform_state.heavy_fields_dirty); + + let mut old_block = make_extended_block_info(8); + old_block.basic_info_mut().time_ms = 1_000_001; + platform + .update_state_cache_v0( + old_block, + block_platform_state, + &transaction, + platform_version, + ) + .expect("block 8 must be stored"); + + let reloaded = Platform::::fetch_platform_state( + &platform.drive, + Some(&transaction), + platform_version, + ) + .expect("fetch must succeed") + .expect("a state was stored"); + + assert_eq!( + reloaded.last_committed_block_height(), + 8, + "block info comes from the small record written at block 8" + ); + assert!( + reloaded.full_masternode_list().contains_key(&pro_tx_hash), + "heavy fields come from the full record written at block 7" + ); + + // The full record on disk must still be block 7's: that is what proves + // block 8 skipped it rather than rewriting it with the same contents. + let full_bytes = platform + .drive + .fetch_platform_state_bytes(Some(&transaction), platform_version) + .expect("fetch must succeed") + .expect("a full record was stored"); + let full_record = PlatformState::versioned_deserialize(&full_bytes, platform_version) + .expect("full record must deserialize"); + assert_eq!( + full_record.last_committed_block_height(), + 7, + "the full record is not rewritten for a historical block that changed nothing heavy" + ); + } } diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs index 0c5750d8947..06234489047 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs @@ -109,6 +109,23 @@ where .. } = &masternode_diff; + // Core advances a block without any masternode changing far more often + // than not. Returning before the first mutable borrow keeps the platform + // state clean, which is what lets the block skip rewriting the full saved + // state (over a megabyte on mainnet) to disk. + if !start_from_scratch + && added_mns.is_empty() + && removed_mns.is_empty() + && updated_mns.is_empty() + { + return Ok( + update_state_masternode_list_outcome::v0::UpdateStateMasternodeListOutcome { + masternode_list_diff: masternode_diff, + removed_masternodes: BTreeMap::new(), + }, + ); + } + //todo: clean up let added_hpmns = added_mns.iter().filter_map(|masternode| { if masternode.node_type == MasternodeType::Evo { diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs index 0d6cf494ef7..f834c03f420 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs @@ -117,27 +117,34 @@ where .into_iter() .collect(); - let mut removed_a_validator_set = false; + // Checked before taking a mutable borrow: on most blocks Core reports the + // same quorums as the block before, and taking the borrow marks the whole + // platform state as needing a full rewrite to disk. + let removed_a_validator_set = block_platform_state + .validator_sets() + .keys() + .any(|quorum_hash| !validator_quorums_list.contains_key::(quorum_hash)); // Remove validator_sets entries that are no longer valid for the core block height - block_platform_state - .validator_sets_mut() - .retain(|quorum_hash, _| { - let retain = validator_quorums_list.contains_key::(quorum_hash); - removed_a_validator_set |= !retain; - - if !retain { - tracing::trace!( - ?quorum_hash, - quorum_type = ?self.config.validator_set.quorum_type, - "removed validator set {} with quorum type {}", - quorum_hash, - self.config.validator_set.quorum_type - ) - } + if removed_a_validator_set { + block_platform_state + .validator_sets_mut() + .retain(|quorum_hash, _| { + let retain = validator_quorums_list.contains_key::(quorum_hash); + + if !retain { + tracing::trace!( + ?quorum_hash, + quorum_type = ?self.config.validator_set.quorum_type, + "removed validator set {} with quorum type {}", + quorum_hash, + self.config.validator_set.quorum_type + ) + } - retain - }); + retain + }); + } // Fetch quorum info and their keys from the RPC for new quorums let mut quorum_infos = validator_quorums_list @@ -192,25 +199,28 @@ where let is_validator_set_updated = !new_validator_sets.is_empty() || removed_a_validator_set; - // Add new validator_sets entries - block_platform_state - .validator_sets_mut() - .extend(new_validator_sets); - - // Sort all validator sets into deterministic order by core block height of creation - block_platform_state - .validator_sets_mut() - .sort_by(|_, quorum_a, _, quorum_b| { - let primary_comparison = quorum_b.core_height().cmp(&quorum_a.core_height()); - if primary_comparison == std::cmp::Ordering::Equal { - quorum_b - .quorum_hash() - .cmp(quorum_a.quorum_hash()) - .then_with(|| quorum_b.core_height().cmp(&quorum_a.core_height())) - } else { - primary_comparison - } - }); + // Add new validator_sets entries. Nothing added and nothing removed means + // the map is already the one the previous block sorted, so leave it be. + if is_validator_set_updated { + block_platform_state + .validator_sets_mut() + .extend(new_validator_sets); + + // Sort all validator sets into deterministic order by core block height of creation + block_platform_state + .validator_sets_mut() + .sort_by(|_, quorum_a, _, quorum_b| { + let primary_comparison = quorum_b.core_height().cmp(&quorum_a.core_height()); + if primary_comparison == std::cmp::Ordering::Equal { + quorum_b + .quorum_hash() + .cmp(quorum_a.quorum_hash()) + .then_with(|| quorum_b.core_height().cmp(&quorum_a.core_height())) + } else { + primary_comparison + } + }); + } // Update Chain Lock quorums @@ -231,7 +241,7 @@ where } else { self.update_quorums_from_quorum_list( quorum_set_type, - block_platform_state.chain_lock_validating_quorums_mut(), + block_platform_state, platform_state, &extended_quorum_list, last_committed_core_height, @@ -266,7 +276,7 @@ where } else { self.update_quorums_from_quorum_list( quorum_set_type, - block_platform_state.instant_lock_validating_quorums_mut(), + block_platform_state, platform_state, &extended_quorum_list, last_committed_core_height, @@ -319,7 +329,7 @@ where fn update_quorums_from_quorum_list( &self, quorum_set_type: QuorumSetType, - quorum_set: &mut SignatureVerificationQuorumSet, + block_platform_state: &mut PlatformState, platform_state: Option<&PlatformState>, full_quorum_list: &ExtendedQuorumListResult, last_committed_core_height: u32, @@ -341,6 +351,25 @@ where }) .collect(); + // Core reports the same quorums on most blocks. Decide read-only whether + // anything moved, because reaching for the mutable quorum set marks the + // whole platform state as needing a full rewrite to disk. + { + let current = + quorum_set_by_type(block_platform_state, &quorum_set_type).current_quorums(); + let unchanged = current.len() == quorums_list.len() + && current.iter().all(|(quorum_hash, quorum)| { + quorums_list + .get(quorum_hash) + .is_some_and(|index| *index == quorum.index) + }); + if unchanged { + return Ok(false); + } + } + + let quorum_set = quorum_set_by_type_mut(block_platform_state, &quorum_set_type); + let mut removed_a_validating_quorum = false; // Remove validating_quorums entries that are no longer valid for the core block height diff --git a/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/v0/mod.rs b/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/v0/mod.rs index a815e0266a6..bc624eb538e 100644 --- a/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/v0/mod.rs @@ -1,6 +1,8 @@ use crate::error::Error; use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::recent::PlatformStateRecent; use crate::platform_types::platform_state::PlatformState; +use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; use dpp::serialization::PlatformDeserializableFromVersionedStructure; use dpp::version::PlatformVersion; use drive::drive::Drive; @@ -12,23 +14,43 @@ impl Platform { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { - drive + let Some(bytes) = drive .fetch_platform_state_bytes(transaction, platform_version) .map_err(Error::Drive)? - .map(|bytes| { - let result = PlatformState::versioned_deserialize(&bytes, platform_version) - .map_err(Error::Protocol); + else { + return Ok(None); + }; - if result.is_err() { - tracing::error!( - bytes = hex::encode(&bytes), - "Unable deserialize platform state for version {}", - platform_version.protocol_version - ); - } - - result + let mut state = PlatformState::versioned_deserialize(&bytes, platform_version) + .inspect_err(|_| { + tracing::error!( + bytes = hex::encode(&bytes), + "Unable deserialize platform state for version {}", + platform_version.protocol_version + ); }) - .transpose() + .map_err(Error::Protocol)?; + + // The full record is only rewritten when a heavy field changes, so a + // newer small record holds the block info and quorum hashes for the + // blocks since. An older one (or none, on a database written before this + // existed) is ignored: the full record already has those fields. + if let Some(recent_bytes) = drive + .fetch_platform_state_recent_bytes(transaction, platform_version) + .map_err(Error::Drive)? + { + let recent = PlatformStateRecent::deserialize(&recent_bytes)?; + + if recent.height() + >= state + .last_committed_block_info + .as_ref() + .map(|i| i.basic_info().height) + { + recent.apply_to(&mut state); + } + } + + Ok(Some(state)) } } diff --git a/packages/rs-drive-abci/src/execution/storage/store_platform_state/v0/mod.rs b/packages/rs-drive-abci/src/execution/storage/store_platform_state/v0/mod.rs index a6d79b2b483..b43982d0e54 100644 --- a/packages/rs-drive-abci/src/execution/storage/store_platform_state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/storage/store_platform_state/v0/mod.rs @@ -1,6 +1,8 @@ use crate::error::Error; use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::recent::PlatformStateRecent; use crate::platform_types::platform_state::PlatformState; +use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; use dpp::serialization::PlatformSerializable; use dpp::version::PlatformVersion; use drive::query::TransactionArg; @@ -13,21 +15,44 @@ impl Platform { platform_version: &PlatformVersion, ) -> Result<(), Error> { #[cfg(feature = "testing-config")] - { - if self.config.testing_configs.store_platform_state { + let should_store = self.config.testing_configs.store_platform_state; + #[cfg(not(feature = "testing-config"))] + let should_store = true; + + if should_store { + // The masternode lists, validator sets and quorum sets are most of + // the record — over a megabyte on mainnet — and only change when + // Core's do, which is a minority of blocks. While replaying history + // the full record is rewritten only when one of them moved, and the + // small record below carries the per-block fields in between. Both + // are written in the block's transaction, so a reader never sees + // them disagree. + // + // Once the node is at the tip the full record is written every block + // again, so a node that is up to date always has a complete record on + // disk and an older drive-abci — which knows nothing about the small + // record — can still read it. Skipping is confined to a node that is + // catching up, where the remedy for any format trouble is the resync + // it is already doing. + if state.heavy_fields_dirty + || !state + .last_committed_block_info + .as_ref() + .is_some_and(|info| { + crate::utils::is_historical_block(info.basic_info().time_ms) + }) + { + let bytes = state.serialize_to_bytes()?; self.drive - .store_platform_state_bytes( - &state.serialize_to_bytes()?, - transaction, - platform_version, - ) + .store_platform_state_bytes(&bytes, transaction, platform_version) .map_err(Error::Drive)?; } + + let recent_bytes = PlatformStateRecent::from(state).serialize_to_bytes()?; + self.drive + .store_platform_state_recent_bytes(&recent_bytes, transaction, platform_version) + .map_err(Error::Drive)?; } - #[cfg(not(feature = "testing-config"))] - self.drive - .store_platform_state_bytes(&state.serialize_to_bytes()?, transaction, platform_version) - .map_err(Error::Drive)?; // We need to persist new protocol version as well be able to read block state self.drive diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/accessors.rs b/packages/rs-drive-abci/src/platform_types/platform_state/accessors.rs index 3130a567aa9..86bf3d59dd5 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/accessors.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/accessors.rs @@ -399,6 +399,10 @@ impl PlatformStateV0Methods for PlatformState { /// Sets the current protocol version in consensus. fn set_current_protocol_version_in_consensus(&mut self, version: ProtocolVersion) { self.current_protocol_version_in_consensus = version; + // The protocol version chooses the structure the full record is written + // in, so a change has to rewrite it rather than leave an older structure + // on disk with a newer version recorded beside it. + self.heavy_fields_dirty = true; } /// Sets the next epoch protocol version. @@ -419,26 +423,31 @@ impl PlatformStateV0Methods for PlatformState { /// Sets the current validator sets. fn set_validator_sets(&mut self, sets: IndexMap) { self.validator_sets = sets; + self.heavy_fields_dirty = true; } /// Sets the current chain lock validating quorums. fn set_chain_lock_validating_quorums(&mut self, quorums: SignatureVerificationQuorumSet) { self.chain_lock_validating_quorums = quorums; + self.heavy_fields_dirty = true; } /// Sets the current instant lock validating quorums. fn set_instant_lock_validating_quorums(&mut self, quorums: SignatureVerificationQuorumSet) { self.instant_lock_validating_quorums = quorums; + self.heavy_fields_dirty = true; } /// Sets the full masternode list. fn set_full_masternode_list(&mut self, list: BTreeMap) { self.full_masternode_list = list; + self.heavy_fields_dirty = true; } /// Sets the list of high performance masternodes. fn set_hpmn_masternode_list(&mut self, list: BTreeMap) { self.hpmn_masternode_list = list; + self.heavy_fields_dirty = true; } /// Sets the platform initialization information. @@ -451,6 +460,7 @@ impl PlatformStateV0Methods for PlatformState { } fn current_protocol_version_in_consensus_mut(&mut self) -> &mut ProtocolVersion { + self.heavy_fields_dirty = true; &mut self.current_protocol_version_in_consensus } @@ -467,22 +477,27 @@ impl PlatformStateV0Methods for PlatformState { } fn validator_sets_mut(&mut self) -> &mut IndexMap { + self.heavy_fields_dirty = true; &mut self.validator_sets } fn chain_lock_validating_quorums_mut(&mut self) -> &mut SignatureVerificationQuorumSet { + self.heavy_fields_dirty = true; &mut self.chain_lock_validating_quorums } fn instant_lock_validating_quorums_mut(&mut self) -> &mut SignatureVerificationQuorumSet { + self.heavy_fields_dirty = true; &mut self.instant_lock_validating_quorums } fn full_masternode_list_mut(&mut self) -> &mut BTreeMap { + self.heavy_fields_dirty = true; &mut self.full_masternode_list } fn hpmn_masternode_list_mut(&mut self) -> &mut BTreeMap { + self.heavy_fields_dirty = true; &mut self.hpmn_masternode_list } @@ -606,6 +621,124 @@ impl PlatformStateV0Methods for PlatformState { } fn previous_fee_versions_mut(&mut self) -> &mut CachedEpochIndexFeeVersions { + self.heavy_fields_dirty = true; &mut self.previous_fee_versions } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::PlatformConfig; + use dpp::dashcore::hashes::Hash; + use dpp::dashcore::Network; + + fn clean_state() -> PlatformState { + let platform_version = PlatformVersion::latest(); + let mut state = PlatformState::default_with_protocol_versions( + platform_version.protocol_version, + platform_version.protocol_version, + &PlatformConfig::default_for_network(Network::Testnet), + ) + .expect("platform state"); + state.heavy_fields_dirty = false; + state + } + + /// Runs `mutate` on a clean state and reports whether it left the state dirty. + fn leaves_dirty(mutate: impl FnOnce(&mut PlatformState)) -> bool { + let mut state = clean_state(); + mutate(&mut state); + state.heavy_fields_dirty + } + + /// A state that has never been written in full must start dirty, or its + /// first historical block would skip the full write. + #[test] + fn a_new_state_starts_dirty() { + let platform_version = PlatformVersion::latest(); + let state = PlatformState::default_with_protocol_versions( + platform_version.protocol_version, + platform_version.protocol_version, + &PlatformConfig::default_for_network(Network::Testnet), + ) + .expect("platform state"); + + assert!(state.heavy_fields_dirty); + } + + /// Every accessor that can change a field carried only by the full saved + /// record must mark the state dirty. If one stops doing so, a historical + /// block that changes that field through it skips the full write, and a + /// node restarted from disk comes back with the old value. + #[test] + fn heavy_field_accessors_mark_the_state_dirty() { + let quorums = clean_state().chain_lock_validating_quorums().clone(); + + assert!(leaves_dirty( + |s| s.set_current_protocol_version_in_consensus(1) + )); + assert!(leaves_dirty(|s| s.set_validator_sets(IndexMap::new()))); + assert!(leaves_dirty( + |s| s.set_chain_lock_validating_quorums(quorums.clone()) + )); + assert!(leaves_dirty( + |s| s.set_instant_lock_validating_quorums(quorums.clone()) + )); + assert!(leaves_dirty(|s| s.set_full_masternode_list(BTreeMap::new()))); + assert!(leaves_dirty(|s| s.set_hpmn_masternode_list(BTreeMap::new()))); + + // Handing out the mutable borrow is enough: the caller may change the + // field through it without the state seeing the write. + assert!(leaves_dirty(|s| { + s.current_protocol_version_in_consensus_mut(); + })); + assert!(leaves_dirty(|s| { + s.validator_sets_mut(); + })); + assert!(leaves_dirty(|s| { + s.chain_lock_validating_quorums_mut(); + })); + assert!(leaves_dirty(|s| { + s.instant_lock_validating_quorums_mut(); + })); + assert!(leaves_dirty(|s| { + s.full_masternode_list_mut(); + })); + assert!(leaves_dirty(|s| { + s.hpmn_masternode_list_mut(); + })); + assert!(leaves_dirty(|s| { + s.previous_fee_versions_mut(); + })); + } + + /// The fields the small per-block record carries are written every block + /// regardless, so changing them must not force a full rewrite. + #[test] + fn per_block_field_accessors_leave_the_state_clean() { + assert!(!leaves_dirty(|s| s.set_last_committed_block_info(None))); + assert!(!leaves_dirty(|s| s.set_next_epoch_protocol_version(1))); + assert!(!leaves_dirty(|s| { + s.set_current_validator_set_quorum_hash(QuorumHash::all_zeros()) + })); + assert!(!leaves_dirty(|s| s.set_next_validator_set_quorum_hash(None))); + assert!(!leaves_dirty(|s| s.set_genesis_block_info(None))); + assert!(!leaves_dirty(|s| { + s.take_next_validator_set_quorum_hash(); + })); + + assert!(!leaves_dirty(|s| { + s.last_committed_block_info_mut(); + })); + assert!(!leaves_dirty(|s| { + s.next_epoch_protocol_version_mut(); + })); + assert!(!leaves_dirty(|s| { + s.current_validator_set_quorum_hash_mut(); + })); + assert!(!leaves_dirty(|s| { + s.next_validator_set_quorum_hash_mut(); + })); + } +} diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs index 81f438fe51a..9005da2f9e9 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs @@ -1,6 +1,7 @@ mod accessors; mod masternode_list_changes; mod platform_state_for_saving; +pub mod recent; use crate::error::Error; @@ -64,6 +65,14 @@ pub struct PlatformState { /// previous FeeVersions pub previous_fee_versions: CachedEpochIndexFeeVersions, + + /// True when a field carried only by the full saved record has changed since + /// the state was last written in full. The masternode lists, validator sets + /// and quorum sets are over a megabyte on mainnet and change on a minority of + /// blocks, so the full record is rewritten only when this is set; every block + /// still writes the small record holding the block info and quorum hashes. + /// Not part of the saved record: a state read back from disk starts dirty. + pub heavy_fields_dirty: bool, } fn hex_encoded_validator_sets(validator_sets: &IndexMap) -> String { @@ -149,6 +158,7 @@ impl PlatformState { hpmn_masternode_list: Default::default(), genesis_block_info: None, previous_fee_versions: Default::default(), + heavy_fields_dirty: true, }; Ok(state) @@ -162,7 +172,7 @@ impl PlatformSerializable for PlatformState { let platform_version = self.current_platform_version()?; let config = config::standard().with_big_endian().with_no_limit(); let platform_state_for_saving: PlatformStateForSaving = - self.clone().try_into_platform_versioned(platform_version)?; + self.try_into_platform_versioned(platform_version)?; bincode::encode_to_vec(platform_state_for_saving, config).map_err(|e| { ProtocolError::PlatformSerializationError(format!( "unable to serialize PlatformState: {}", @@ -198,6 +208,31 @@ impl PlatformDeserializableFromVersionedStructure for PlatformState { } } +impl TryFromPlatformVersioned<&PlatformState> for PlatformStateForSaving { + type Error = Error; + fn try_from_platform_versioned( + value: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive_abci + .structs + .platform_state_for_saving_structure_default + { + 0 => { + let saving_v1: PlatformStateForSavingV1 = value.try_into()?; + Ok(saving_v1.into()) + } + version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch { + method: "PlatformStateForSaving::try_from_platform_versioned(&PlatformState)" + .to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} + impl TryFromPlatformVersioned for PlatformStateForSaving { type Error = Error; fn try_from_platform_versioned( @@ -281,6 +316,25 @@ mod tests { .expect("failed to deserialize state"); } + /// Serializing through the borrowed conversion must preserve the saved format. + #[test] + fn should_preserve_pre_change_serialization_hash() { + let serialized_state = + hex::decode(PLATFORM_STATE_V8_DEVNET.deref()).expect("failed to decode hex"); + + let state = PlatformState::versioned_deserialize(&serialized_state, &PLATFORM_V9) + .expect("failed to deserialize state"); + + // Generated with serialize_to_bytes() at pre-change commit + // 9dfffa611a9554cb14c9464374c8de1356c1d92f, using the fixture above. + assert_eq!( + hex::encode(hash_double( + state.serialize_to_bytes().expect("borrowed serialize") + )), + "079e5cb38c07a9e1818a4a71a40fe8936e7c93bf2fec39c7d346afa215b76679" + ); + } + #[test] fn should_deserialize_state_stored_in_version_8_from_devnet() { let serialized_state = diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/mod.rs index d3e4f334b74..e9f28576faf 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/mod.rs @@ -87,6 +87,8 @@ impl From for PlatformState { .into_keys() .map(|epoch_index| (epoch_index, FeeVersion::first())) .collect(), + // a state read back from disk has not been written in full since + heavy_fields_dirty: true, } } } diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v1/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v1/mod.rs index 14230624b12..1a03e27c6d4 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v1/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v1/mod.rs @@ -53,14 +53,19 @@ pub struct PlatformStateForSavingV1 { pub previous_fee_versions: EpochIndexFeeVersionsForStorage, } -impl TryFrom for PlatformStateForSavingV1 { +impl TryFrom<&PlatformState> for PlatformStateForSavingV1 { type Error = Error; - fn try_from(value: PlatformState) -> Result { + /// Builds the saving form from a borrowed state, cloning each field once. + /// + /// Serialization used to go through the owned conversion, which meant + /// cloning the whole state first: two full copies of the masternode lists + /// and validator sets per block, on a path that runs once per block. + fn try_from(value: &PlatformState) -> Result { let platform_version = value.current_platform_version()?; Ok(PlatformStateForSavingV1 { genesis_block_info: value.genesis_block_info, - last_committed_block_info: value.last_committed_block_info, + last_committed_block_info: value.last_committed_block_info.clone(), current_protocol_version_in_consensus: value.current_protocol_version_in_consensus, next_epoch_protocol_version: value.next_epoch_protocol_version, current_validator_set_quorum_hash: value @@ -72,40 +77,48 @@ impl TryFrom for PlatformStateForSavingV1 { .map(|quorum_hash| quorum_hash.to_byte_array().into()), validator_sets: value .validator_sets - .into_iter() - .map(|(k, v)| (k.to_byte_array().into(), v)) + .iter() + .map(|(k, v)| (k.to_byte_array().into(), v.clone())) .collect(), - chain_lock_validating_quorums: value.chain_lock_validating_quorums.into(), - instant_lock_validating_quorums: value.instant_lock_validating_quorums.into(), + chain_lock_validating_quorums: value.chain_lock_validating_quorums.clone().into(), + instant_lock_validating_quorums: value.instant_lock_validating_quorums.clone().into(), full_masternode_list: value .full_masternode_list - .into_iter() + .iter() .map(|(k, v)| { Ok(( k.to_byte_array().into(), - v.try_into_platform_versioned(platform_version)?, + v.clone().try_into_platform_versioned(platform_version)?, )) }) .collect::, Error>>()?, hpmn_masternode_list: value .hpmn_masternode_list - .into_iter() + .iter() .map(|(k, v)| { Ok(( k.to_byte_array().into(), - v.try_into_platform_versioned(platform_version)?, + v.clone().try_into_platform_versioned(platform_version)?, )) }) .collect::, Error>>()?, previous_fee_versions: value .previous_fee_versions - .into_iter() - .map(|(epoch_index, fee_version)| (epoch_index, fee_version.fee_version_number)) + .iter() + .map(|(epoch_index, fee_version)| (*epoch_index, fee_version.fee_version_number)) .collect(), }) } } +impl TryFrom for PlatformStateForSavingV1 { + type Error = Error; + + fn try_from(value: PlatformState) -> Result { + (&value).try_into() + } +} + impl From for PlatformState { fn from(value: PlatformStateForSavingV1) -> Self { PlatformState { @@ -147,6 +160,8 @@ impl From for PlatformState { ) }) .collect(), + // a state read back from disk has not been written in full since + heavy_fields_dirty: true, } } } diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/recent.rs b/packages/rs-drive-abci/src/platform_types/platform_state/recent.rs new file mode 100644 index 00000000000..43e573cb857 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/platform_state/recent.rs @@ -0,0 +1,118 @@ +//! The part of the platform state that changes on every block. +//! +//! The full saved state is over a megabyte on mainnet — masternode lists, +//! validator sets and the chain-lock and instant-lock quorum sets — and those +//! parts only change when Core's masternode list or quorums do. This record +//! carries the rest, so a block that changed nothing heavy writes a couple of +//! hundred bytes instead of rewriting the whole state. + +use crate::error::Error; +use crate::platform_types::platform_state::PlatformState; +use bincode::{Decode, Encode}; +use dpp::block::block_info::BlockInfo; +use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; +use dpp::block::extended_block_info::ExtendedBlockInfo; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::QuorumHash; +use dpp::platform_value::Bytes32; +use dpp::util::deserializer::ProtocolVersion; +use dpp::ProtocolError; + +/// Versioned per-block platform state record. +#[derive(Clone, Debug, Encode, Decode)] +pub enum PlatformStateRecent { + /// Version 0 + V0(PlatformStateRecentV0), +} + +/// Version 0 of the per-block platform state record. +#[derive(Clone, Debug, Encode, Decode)] +pub struct PlatformStateRecentV0 { + /// Information about the genesis block + pub genesis_block_info: Option, + /// Information about the last block + pub last_committed_block_info: Option, + /// Current version + pub current_protocol_version_in_consensus: ProtocolVersion, + /// Upcoming protocol version + pub next_epoch_protocol_version: ProtocolVersion, + /// Current quorum + pub current_validator_set_quorum_hash: Bytes32, + /// Next quorum + pub next_validator_set_quorum_hash: Option, +} + +impl From<&PlatformState> for PlatformStateRecent { + fn from(state: &PlatformState) -> Self { + PlatformStateRecent::V0(PlatformStateRecentV0 { + genesis_block_info: state.genesis_block_info, + last_committed_block_info: state.last_committed_block_info.clone(), + current_protocol_version_in_consensus: state.current_protocol_version_in_consensus, + next_epoch_protocol_version: state.next_epoch_protocol_version, + current_validator_set_quorum_hash: state + .current_validator_set_quorum_hash + .to_byte_array() + .into(), + next_validator_set_quorum_hash: state + .next_validator_set_quorum_hash + .map(|hash| hash.to_byte_array().into()), + }) + } +} + +impl PlatformStateRecent { + fn bincode_config() -> bincode::config::Configuration< + bincode::config::BigEndian, + bincode::config::Varint, + bincode::config::NoLimit, + > { + bincode::config::standard() + .with_big_endian() + .with_no_limit() + } + + /// Encodes the record for the `saved_state_recent` aux key. + pub fn serialize_to_bytes(&self) -> Result, Error> { + bincode::encode_to_vec(self, Self::bincode_config()).map_err(|e| { + Error::Protocol(ProtocolError::PlatformSerializationError(format!( + "unable to serialize recent platform state: {e}" + ))) + }) + } + + /// Decodes a record written by [`serialize_to_bytes`](Self::serialize_to_bytes). + pub fn deserialize(bytes: &[u8]) -> Result { + bincode::decode_from_slice(bytes, Self::bincode_config()) + .map(|(record, _)| record) + .map_err(|e| { + Error::Protocol(ProtocolError::PlatformDeserializationError(format!( + "unable to deserialize recent platform state: {e}" + ))) + }) + } + + /// Overwrite the per-block fields of `state` with the ones in this record. + /// + /// The heavy fields are left alone: they came from a full record written at + /// or before the height this record was written at, and are unchanged since. + pub fn apply_to(self, state: &mut PlatformState) { + let PlatformStateRecent::V0(v0) = self; + state.genesis_block_info = v0.genesis_block_info; + state.last_committed_block_info = v0.last_committed_block_info; + state.current_protocol_version_in_consensus = v0.current_protocol_version_in_consensus; + state.next_epoch_protocol_version = v0.next_epoch_protocol_version; + state.current_validator_set_quorum_hash = + QuorumHash::from_byte_array(v0.current_validator_set_quorum_hash.to_buffer()); + state.next_validator_set_quorum_hash = v0 + .next_validator_set_quorum_hash + .map(|bytes| QuorumHash::from_byte_array(bytes.to_buffer())); + } + + /// The height this record was written at, if it has block info. + pub fn height(&self) -> Option { + let PlatformStateRecent::V0(v0) = self; + v0.last_committed_block_info + .as_ref() + .map(|info| info.basic_info().height) + } +} diff --git a/packages/rs-drive/src/drive/platform_state/fetch_platform_state_recent_bytes/mod.rs b/packages/rs-drive/src/drive/platform_state/fetch_platform_state_recent_bytes/mod.rs new file mode 100644 index 00000000000..38f4b8696ab --- /dev/null +++ b/packages/rs-drive/src/drive/platform_state/fetch_platform_state_recent_bytes/mod.rs @@ -0,0 +1,30 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; + +impl Drive { + /// Fetches the per-block part of the platform state, if one was ever written. + pub fn fetch_platform_state_recent_bytes( + &self, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result>, Error> { + match platform_version + .drive + .methods + .platform_state + .fetch_platform_state_recent_bytes + { + 0 => self.fetch_platform_state_recent_bytes_v0(transaction), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_platform_state_recent_bytes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/platform_state/fetch_platform_state_recent_bytes/v0/mod.rs b/packages/rs-drive/src/drive/platform_state/fetch_platform_state_recent_bytes/v0/mod.rs new file mode 100644 index 00000000000..211ec69817c --- /dev/null +++ b/packages/rs-drive/src/drive/platform_state/fetch_platform_state_recent_bytes/v0/mod.rs @@ -0,0 +1,16 @@ +use crate::drive::platform_state::PLATFORM_STATE_RECENT_KEY; +use crate::drive::Drive; +use crate::error::Error; +use grovedb::TransactionArg; + +impl Drive { + pub(super) fn fetch_platform_state_recent_bytes_v0( + &self, + transaction: TransactionArg, + ) -> Result>, Error> { + self.grove + .get_aux(PLATFORM_STATE_RECENT_KEY, transaction) + .unwrap() + .map_err(Error::from) + } +} diff --git a/packages/rs-drive/src/drive/platform_state/mod.rs b/packages/rs-drive/src/drive/platform_state/mod.rs index d6a0ce16c49..9e619991943 100644 --- a/packages/rs-drive/src/drive/platform_state/mod.rs +++ b/packages/rs-drive/src/drive/platform_state/mod.rs @@ -1,4 +1,13 @@ mod fetch_platform_state_bytes; +mod fetch_platform_state_recent_bytes; mod store_platform_state_bytes; +mod store_platform_state_recent_bytes; const PLATFORM_STATE_KEY: &[u8; 11] = b"saved_state"; + +/// The small companion to [`PLATFORM_STATE_KEY`]: the fields of the platform +/// state that change on every block. The full record is rewritten only when the +/// masternode lists, validator sets or quorum sets change, so this one carries +/// the block info in between. Both are written in the block's transaction, so a +/// reader always sees a pair that committed together. +const PLATFORM_STATE_RECENT_KEY: &[u8; 18] = b"saved_state_recent"; diff --git a/packages/rs-drive/src/drive/platform_state/store_platform_state_recent_bytes/mod.rs b/packages/rs-drive/src/drive/platform_state/store_platform_state_recent_bytes/mod.rs new file mode 100644 index 00000000000..d9b3b6bdd2e --- /dev/null +++ b/packages/rs-drive/src/drive/platform_state/store_platform_state_recent_bytes/mod.rs @@ -0,0 +1,35 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; + +impl Drive { + /// Stores the per-block part of the platform state in auxiliary storage. + /// + /// The full record under `saved_state` is rewritten only when its heavy + /// fields change; this small companion carries the block info and quorum + /// hashes for every block in between. + pub fn store_platform_state_recent_bytes( + &self, + state_bytes: &[u8], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .platform_state + .store_platform_state_recent_bytes + { + 0 => self.store_platform_state_recent_bytes_v0(state_bytes, transaction), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "store_platform_state_recent_bytes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/platform_state/store_platform_state_recent_bytes/v0/mod.rs b/packages/rs-drive/src/drive/platform_state/store_platform_state_recent_bytes/v0/mod.rs new file mode 100644 index 00000000000..9db8c81ccb4 --- /dev/null +++ b/packages/rs-drive/src/drive/platform_state/store_platform_state_recent_bytes/v0/mod.rs @@ -0,0 +1,17 @@ +use crate::drive::platform_state::PLATFORM_STATE_RECENT_KEY; +use crate::drive::Drive; +use crate::error::Error; +use grovedb::TransactionArg; + +impl Drive { + pub(super) fn store_platform_state_recent_bytes_v0( + &self, + state_bytes: &[u8], + transaction: TransactionArg, + ) -> Result<(), Error> { + self.grove + .put_aux(PLATFORM_STATE_RECENT_KEY, state_bytes, None, transaction) + .unwrap() + .map_err(Error::from) + } +} diff --git a/packages/rs-platform-version/src/version/drive_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/mod.rs index ca7c22c6e3f..b9724e8f7be 100644 --- a/packages/rs-platform-version/src/version/drive_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/mod.rs @@ -78,6 +78,8 @@ pub struct DriveMethodVersions { pub struct DrivePlatformStateMethodVersions { pub fetch_platform_state_bytes: FeatureVersion, pub store_platform_state_bytes: FeatureVersion, + pub fetch_platform_state_recent_bytes: FeatureVersion, + pub store_platform_state_recent_bytes: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/v1.rs index 6e87d8fe420..287b04d617e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v1.rs @@ -90,6 +90,8 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/v2.rs index 0fe4f8f235e..0cbc1223a34 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v2.rs @@ -90,6 +90,8 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v3.rs b/packages/rs-platform-version/src/version/drive_versions/v3.rs index a542fe99e85..a2bd5d0003f 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v3.rs @@ -90,6 +90,8 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v4.rs b/packages/rs-platform-version/src/version/drive_versions/v4.rs index 4481d8b90ac..0ac9ffd99b5 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v4.rs @@ -90,6 +90,8 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v5.rs b/packages/rs-platform-version/src/version/drive_versions/v5.rs index bfbce3d74b1..917424386a9 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v5.rs @@ -92,6 +92,8 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v6.rs b/packages/rs-platform-version/src/version/drive_versions/v6.rs index 304cbdb70c7..2e1e5acc9bf 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v6.rs @@ -94,6 +94,8 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs index 05d8ad2d05e..a983c13ee4e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -92,6 +92,8 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v8.rs b/packages/rs-platform-version/src/version/drive_versions/v8.rs index 7f421173191..9aad5317f52 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v8.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v8.rs @@ -92,6 +92,8 @@ pub const DRIVE_VERSION_V8: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/drive_versions/v9.rs b/packages/rs-platform-version/src/version/drive_versions/v9.rs index fade08c521d..63fd373af4b 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v9.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v9.rs @@ -106,6 +106,8 @@ pub const DRIVE_VERSION_V9: DriveVersion = DriveVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index 76579f3a0d7..543d7bdc711 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -128,6 +128,8 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { platform_state: DrivePlatformStateMethodVersions { fetch_platform_state_bytes: 0, store_platform_state_bytes: 0, + fetch_platform_state_recent_bytes: 0, + store_platform_state_recent_bytes: 0, }, fetch: DriveFetchMethodVersions { fetch_elements: 0 }, prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions {