stratus/eth/storage/permanent/rocks/
cf_versions.rs

1//! Column Family (CF) versioning.
2//!
3//! This allows our KV-store to have different versions on the Value.
4//!
5//! Versions are tested against snapshots to avoid breaking changes.
6
7use std::fmt::Debug;
8use std::ops::Deref;
9use std::ops::DerefMut;
10
11use serde::Deserialize;
12use serde::Serialize;
13use stratus_macros::FakeEnum;
14use strum::EnumCount;
15use strum::IntoStaticStr;
16use strum::VariantNames;
17
18use super::types::AccountRocksdb;
19use super::types::BlockNumberRocksdb;
20use super::types::BlockRocksdb;
21use super::types::SlotValueRocksdb;
22use crate::eth::primitives::Account;
23use crate::eth::primitives::Block;
24use crate::eth::primitives::BlockNumber;
25use crate::eth::primitives::SlotValue;
26use crate::eth::storage::permanent::rocks::SerializeDeserializeWithContext;
27#[cfg(test)]
28use crate::eth::storage::permanent::rocks::test_utils::FakeEnum;
29use crate::eth::storage::permanent::rocks::types::BlockChangesRocksdb;
30
31macro_rules! impl_single_version_cf_value {
32    ($name:ident, $inner_type:ty, $non_rocks_equivalent: ty) => {
33        #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, EnumCount, VariantNames, IntoStaticStr, bincode::Encode, bincode::Decode, FakeEnum)]
34        #[cfg_attr(test, derive(fake::Dummy))]
35        #[fake_enum(generate = "crate::utils::test_utils::fake_first")]
36        pub enum $name {
37            V1($inner_type),
38        }
39
40        impl $name {
41            #[allow(dead_code)]
42            pub fn into_inner(self) -> $inner_type {
43                match self {
44                    Self::V1(v1) => v1,
45                }
46            }
47        }
48
49        // `From` conversion should only exist for values with a single version
50        static_assertions::const_assert_eq!($name::COUNT, 1);
51
52        // implement `From` for the v1 type.
53        impl From<$inner_type> for $name {
54            fn from(v1: $inner_type) -> Self {
55                Self::V1(v1)
56            }
57        }
58
59        impl Deref for $name {
60            type Target = $inner_type;
61            fn deref(&self) -> &Self::Target {
62                match self {
63                    Self::V1(v1) => v1,
64                }
65            }
66        }
67
68        impl DerefMut for $name {
69            fn deref_mut(&mut self) -> &mut Self::Target {
70                match self {
71                    Self::V1(v1) => v1,
72                }
73            }
74        }
75
76        // Do `$non_rocks_equivalent -> $inner_type -> $name` in one conversion.
77        impl From<$non_rocks_equivalent> for $name {
78            fn from(value: $non_rocks_equivalent) -> Self {
79                let value = <$inner_type>::from(value);
80                Self::V1(value)
81            }
82        }
83    };
84}
85
86impl_single_version_cf_value!(CfAccountsValue, AccountRocksdb, Account);
87impl_single_version_cf_value!(CfAccountsHistoryValue, AccountRocksdb, Account);
88impl_single_version_cf_value!(CfAccountSlotsValue, SlotValueRocksdb, SlotValue);
89impl_single_version_cf_value!(CfAccountSlotsHistoryValue, SlotValueRocksdb, SlotValue);
90impl_single_version_cf_value!(CfTransactionsValue, BlockNumberRocksdb, BlockNumber);
91impl_single_version_cf_value!(CfBlocksByNumberValue, BlockRocksdb, Block);
92impl_single_version_cf_value!(CfBlocksByHashValue, BlockNumberRocksdb, BlockNumber);
93impl_single_version_cf_value!(CfBlockChangesValue, BlockChangesRocksdb, ());
94
95impl SerializeDeserializeWithContext for CfAccountSlotsHistoryValue {}
96impl SerializeDeserializeWithContext for CfAccountSlotsValue {}
97impl SerializeDeserializeWithContext for CfAccountsHistoryValue {}
98impl SerializeDeserializeWithContext for CfAccountsValue {}
99impl SerializeDeserializeWithContext for CfBlocksByHashValue {}
100impl SerializeDeserializeWithContext for CfTransactionsValue {}
101impl SerializeDeserializeWithContext for CfBlockChangesValue {}
102impl SerializeDeserializeWithContext for CfBlocksByNumberValue {}
103
104#[cfg(test)]
105mod cf_names {
106    use super::*;
107    use crate::eth::storage::permanent::rocks::test_utils::ToFileName;
108    use crate::impl_to_file_name;
109
110    impl_to_file_name!(CfAccountsValue, "accounts");
111    impl_to_file_name!(CfAccountsHistoryValue, "accounts_history");
112    impl_to_file_name!(CfAccountSlotsValue, "account_slots");
113    impl_to_file_name!(CfAccountSlotsHistoryValue, "account_slots_history");
114    impl_to_file_name!(CfTransactionsValue, "transactions");
115    impl_to_file_name!(CfBlocksByNumberValue, "blocks_by_number");
116    impl_to_file_name!(CfBlocksByHashValue, "blocks_by_hash");
117    impl_to_file_name!(CfBlockChangesValue, "block_changes");
118}
119
120/// Test that deserialization works for each variant of the enum.
121///
122/// This is intended to give an error when the following happens:
123///
124/// 1. A new variant is added to the enum.
125/// 2. A variant is renamed.
126/// 3. A variant is removed.
127/// 4. A variant is modified.
128/// 5. A variant is reordered.
129///
130/// Here is a breakdown of why, and how to proceed:
131///
132/// 1. New variants need to be tested, go to the test below and cover it, but watch out for:
133///   - You'll need an ENV VAR to create the new snapshot file.
134///   - When commiting the change, make sure you're just adding your new snapshot, and not editing others by accident.
135/// 2. For renamed variants, because we use bincode, you just need to update the snapshot file.
136///   - Rename it locally.
137/// 3. Previous variants can't be removed as they break our database, because they won't be able to read the older data.
138///   - Don't do it¹.
139/// 4. If you modify a variant, the database won't be able to read it anymore.
140///   - Don't do it¹.
141/// 5. Reordering variants will break deserialization because bincode uses their order to determine the enum tag.
142///   - Don't do it¹.
143///
144/// ¹: if you really want to do it, make sure you can reload your entire database from scratch.
145#[cfg(test)]
146mod tests {
147    use anyhow::Context;
148
149    use super::*;
150    use crate::eth::storage::permanent::rocks::test_utils::EnumCoverageDropBombChecker;
151    use crate::eth::storage::permanent::rocks::test_utils::{self};
152
153    /// Store snapshots of the current serialization format for each version.
154    #[test]
155    fn test_snapshot_bincode_deserialization_for_version_enums() -> anyhow::Result<()> {
156        let mut accounts_checker = EnumCoverageDropBombChecker::<CfAccountsValue>::new();
157        let mut accounts_history_checker = EnumCoverageDropBombChecker::<CfAccountsHistoryValue>::new();
158        let mut account_slots_checker = EnumCoverageDropBombChecker::<CfAccountSlotsValue>::new();
159        let mut account_slots_history_checker = EnumCoverageDropBombChecker::<CfAccountSlotsHistoryValue>::new();
160        let mut transactions_checker = EnumCoverageDropBombChecker::<CfTransactionsValue>::new();
161        let mut blocks_by_number_checker = EnumCoverageDropBombChecker::<CfBlocksByNumberValue>::new();
162        let mut blocks_by_hash_checker = EnumCoverageDropBombChecker::<CfBlocksByHashValue>::new();
163
164        test_utils::verify_fixtures(&mut accounts_checker, "cf_versions").context("failed to verify variants for CfAccountsValue")?;
165        test_utils::verify_fixtures(&mut accounts_history_checker, "cf_versions").context("failed to verify variants for CfAccountsHistoryValue")?;
166        test_utils::verify_fixtures(&mut account_slots_checker, "cf_versions").context("failed to verify variants for CfAccountSlotsValue")?;
167        test_utils::verify_fixtures(&mut account_slots_history_checker, "cf_versions").context("failed to verify variants for CfAccountSlotsHistoryValue")?;
168        test_utils::verify_fixtures(&mut transactions_checker, "cf_versions").context("failed to verify variants for CfTransactionsValue")?;
169        test_utils::verify_fixtures(&mut blocks_by_number_checker, "cf_versions").context("failed to verify variants for CfBlocksByNumberValue")?;
170        test_utils::verify_fixtures(&mut blocks_by_hash_checker, "cf_versions").context("failed to verify variants for CfBlocksByHashValue")?;
171
172        Ok(())
173    }
174}