fuel_core/database/
state.rs

1use fuel_core_chain_config::TableEntry;
2use fuel_core_storage::{
3    ContractsStateKey,
4    Error as StorageError,
5    StorageBatchMutate,
6    tables::ContractsState,
7};
8use fuel_core_types::fuel_types::{
9    Bytes32,
10    ContractId,
11};
12use itertools::Itertools;
13
14pub trait StateInitializer {
15    /// Initialize the state of the contract from all leaves.
16    /// This method is more performant than inserting state one by one.
17    fn init_contract_state<S>(
18        &mut self,
19        contract_id: &ContractId,
20        slots: S,
21    ) -> Result<(), StorageError>
22    where
23        S: Iterator<Item = (Bytes32, Vec<u8>)>;
24
25    /// Updates the state of multiple contracts based on provided state slots.
26    fn update_contract_states(
27        &mut self,
28        states: impl IntoIterator<Item = TableEntry<ContractsState>>,
29    ) -> Result<(), StorageError>;
30}
31
32impl<S> StateInitializer for S
33where
34    S: StorageBatchMutate<ContractsState, Error = StorageError>,
35{
36    fn init_contract_state<I>(
37        &mut self,
38        contract_id: &ContractId,
39        slots: I,
40    ) -> Result<(), StorageError>
41    where
42        I: Iterator<Item = (Bytes32, Vec<u8>)>,
43    {
44        let slots = slots
45            .map(|(key, value)| (ContractsStateKey::new(contract_id, &key), value))
46            .collect_vec();
47        <_ as StorageBatchMutate<ContractsState>>::init_storage(
48            self,
49            &mut slots.iter().map(|(key, value)| (key, value.as_slice())),
50        )
51    }
52
53    /// Updates the state of multiple contracts based on provided state slots.
54    ///
55    /// Grouping: Adjacent state entries sharing the same contract ID are grouped together.
56    ///           This ensures that consecutive entries for the same contract are processed as a single batch.
57    ///
58    /// State Update Process:
59    ///    - All state entries are inserted into the database.
60    ///    - For new contracts (i.e., those without a previously recorded state), the group is
61    ///      first sorted before the state root is calculated. This is a consequence of the
62    ///      batch-insertion logic of MerkleTree::from_set.
63    ///    - For contracts with an existing state, the function updates their state merkle tree
64    ///      calling MerkleTree::update for each state entry in the group in-order.
65    ///
66    /// # Errors
67    /// On any error while accessing the database.
68    fn update_contract_states(
69        &mut self,
70        states: impl IntoIterator<Item = TableEntry<ContractsState>>,
71    ) -> Result<(), StorageError> {
72        states
73            .into_iter()
74            .group_by(|s| *s.key.contract_id())
75            .into_iter()
76            .try_for_each(|(contract_id, entries)| {
77                self.init_contract_state(
78                    &contract_id,
79                    entries
80                        .into_iter()
81                        .map(|e| (*e.key.state_key(), e.value.into())),
82                )
83            })?;
84
85        Ok(())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::database::{
93        Database,
94        database_description::on_chain::OnChain,
95    };
96    use fuel_core_storage::{
97        StorageAsMut,
98        transactional::IntoTransaction,
99    };
100    use fuel_core_types::fuel_types::Bytes32;
101    use rand::Rng;
102
103    fn random_bytes32<R>(rng: &mut R) -> Bytes32
104    where
105        R: Rng + ?Sized,
106    {
107        let mut bytes = [0u8; 32];
108        rng.fill(bytes.as_mut());
109        bytes.into()
110    }
111
112    #[test]
113    fn init_contract_state_works() {
114        use rand::{
115            SeedableRng,
116            rngs::StdRng,
117        };
118
119        let rng = &mut StdRng::seed_from_u64(1234);
120        let r#gen = || Some((random_bytes32(rng), random_bytes32(rng).to_vec()));
121        let data = core::iter::from_fn(r#gen).take(5_000).collect::<Vec<_>>();
122
123        let contract_id = ContractId::from([1u8; 32]);
124        let mut init_database = Database::<OnChain>::default().into_transaction();
125
126        init_database
127            .init_contract_state(&contract_id, data.clone().into_iter())
128            .expect("Should init contract");
129
130        let mut seq_database = Database::<OnChain>::default().into_transaction();
131        for (key, value) in data.iter() {
132            seq_database
133                .storage_as_mut::<ContractsState>()
134                .insert(&ContractsStateKey::new(&contract_id, key), value)
135                .expect("Should insert a state");
136        }
137
138        for (key, value) in data.into_iter() {
139            let init_value = init_database
140                .storage::<ContractsState>()
141                .get(&ContractsStateKey::new(&contract_id, &key))
142                .expect("Should get a state from init database")
143                .unwrap()
144                .into_owned();
145            let seq_value = seq_database
146                .storage::<ContractsState>()
147                .get(&ContractsStateKey::new(&contract_id, &key))
148                .expect("Should get a state from seq database")
149                .unwrap()
150                .into_owned();
151            let value = value.into();
152            assert_eq!(init_value, value);
153            assert_eq!(seq_value, value);
154        }
155    }
156
157    mod update_contract_state {
158        use core::iter::repeat_with;
159        use fuel_core_chain_config::{
160            ContractStateConfig,
161            Randomize,
162        };
163
164        use fuel_core_storage::iter::IteratorOverTable;
165        use rand::{
166            SeedableRng,
167            rngs::StdRng,
168        };
169
170        use super::*;
171
172        #[test]
173        fn states_inserted_into_db() {
174            // given
175            let mut rng = StdRng::seed_from_u64(0);
176            let state_groups = repeat_with(|| TableEntry::randomize(&mut rng))
177                .chunks(100)
178                .into_iter()
179                .map(|chunk| chunk.collect_vec())
180                .take(10)
181                .collect_vec();
182
183            let database = &mut Database::<OnChain>::default();
184
185            // when
186            for group in &state_groups {
187                database
188                    .update_contract_states(group.clone())
189                    .expect("Should insert contract state");
190            }
191
192            // then
193            let mut states_in_db: Vec<_> = database
194                .iter_all::<ContractsState>(None)
195                .collect::<Result<Vec<_>, _>>()
196                .unwrap()
197                .into_iter()
198                .map(|(key, value)| ContractStateConfig {
199                    key: *key.state_key(),
200                    value: value.into(),
201                })
202                .collect();
203
204            let mut original_state = state_groups
205                .into_iter()
206                .flatten()
207                .map(|entry| ContractStateConfig {
208                    key: *entry.key.state_key(),
209                    value: entry.value.into(),
210                })
211                .collect::<Vec<_>>();
212
213            states_in_db.sort();
214            original_state.sort();
215
216            assert_eq!(states_in_db, original_state);
217        }
218    }
219}