forest/db/migration/
v0_22_1.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Migration logic for 0.22.0 to 0.22.1 version.
5//! A `PersistentGraph` column has been introduced to allow for storage of persistent data that
6//! isn't garbage collected. The initial use-case is network upgrade manifest storage.
7
8use crate::Config;
9use crate::blocks::TipsetKey;
10use crate::db::CAR_DB_DIR_NAME;
11use crate::db::db_engine::Db;
12use crate::db::migration::migration_map::MigrationOperationExt as _;
13use crate::db::migration::v0_22_1::paritydb_0_22_0::{DbColumn, ParityDb};
14use crate::rpc::eth::types::EthHash;
15use crate::utils::multihash::prelude::*;
16use anyhow::Context;
17use cid::Cid;
18use fs_extra::dir::CopyOptions;
19use fvm_ipld_encoding::DAG_CBOR;
20use semver::Version;
21use std::path::{Path, PathBuf};
22use strum::IntoEnumIterator;
23use tracing::info;
24
25use super::migration_map::MigrationOperation;
26
27pub(super) struct Migration0_22_0_0_22_1 {
28    from: Version,
29    to: Version,
30}
31
32/// Migrates the database from version 0.22.0 to 0.22.1
33impl MigrationOperation for Migration0_22_0_0_22_1 {
34    fn new(from: Version, to: Version) -> Self
35    where
36        Self: Sized,
37    {
38        Self { from, to }
39    }
40
41    fn from(&self) -> &Version {
42        &self.from
43    }
44
45    fn to(&self) -> &Version {
46        &self.to
47    }
48
49    fn migrate_core(&self, chain_data_path: &Path, _: &Config) -> anyhow::Result<PathBuf> {
50        let old_db = self.old_db_path(chain_data_path);
51        let temp_db = self.temporary_db_path(chain_data_path);
52
53        let old_car_db_path = old_db.join(CAR_DB_DIR_NAME);
54        let temp_car_db_path = temp_db.join(CAR_DB_DIR_NAME);
55
56        // Make sure `car_db` dir exists as it might not be the case when migrating
57        // from older versions.
58        if old_car_db_path.is_dir() {
59            info!(
60                "Copying snapshot from {} to {}",
61                old_db.display(),
62                temp_db.display()
63            );
64
65            fs_extra::copy_items(
66                &[old_car_db_path.as_path()],
67                temp_car_db_path,
68                &CopyOptions::default().copy_inside(true),
69            )?;
70        }
71
72        let db = ParityDb::open(old_db)?;
73
74        // open the new database to migrate data from the old one.
75        let new_db = paritydb_0_22_1::ParityDb::open(&temp_db)?;
76
77        for col in DbColumn::iter() {
78            info!("Migrating column {}", col);
79            let mut res = anyhow::Ok(());
80            match col {
81                DbColumn::GraphDagCborBlake2b256 => {
82                    db.db.iter_column_while(col as u8, |val| {
83                        let hash = MultihashCode::Blake2b256.digest(&val.value);
84                        let cid = Cid::new_v1(DAG_CBOR, hash);
85                        res = new_db
86                            .db
87                            .commit_changes([Db::set_operation(
88                                col as u8,
89                                cid.to_bytes(),
90                                val.value,
91                            )])
92                            .context("failed to commit");
93
94                        if res.is_err() {
95                            return false;
96                        }
97
98                        true
99                    })?;
100                    res?;
101                }
102                DbColumn::EthMappings => {
103                    db.db.iter_column_while(col as u8, |val| {
104                        let tsk: Result<TipsetKey, fvm_ipld_encoding::Error> =
105                            fvm_ipld_encoding::from_slice(&val.value);
106                        if tsk.is_err() {
107                            res = Err(tsk.context("serde error").unwrap_err());
108                            return false;
109                        }
110                        let cid = tsk.unwrap().cid();
111
112                        if cid.is_err() {
113                            res = Err(cid.context("serde error").unwrap_err());
114                            return false;
115                        }
116
117                        let hash: EthHash = cid.unwrap().into();
118                        res = new_db
119                            .db
120                            .commit_changes([Db::set_operation(
121                                col as u8,
122                                hash.0.as_bytes().to_vec(),
123                                val.value,
124                            )])
125                            .context("failed to commit");
126
127                        if res.is_err() {
128                            return false;
129                        }
130
131                        true
132                    })?;
133                    res?;
134                }
135                _ => {
136                    let mut iter = db.db.iter(col as u8)?;
137                    while let Some((key, value)) = iter.next()? {
138                        new_db
139                            .db
140                            .commit_changes([Db::set_operation(col as u8, key, value)])
141                            .context("failed to commit")?;
142                    }
143                }
144            }
145        }
146
147        drop(new_db);
148
149        Ok(temp_db)
150    }
151}
152
153/// Database settings from Forest `v0.22.0`
154mod paritydb_0_22_0 {
155    use parity_db::{CompressionType, Db, Options};
156    use std::path::PathBuf;
157    use strum::{Display, EnumIter, IntoEnumIterator};
158
159    #[derive(Copy, Clone, Debug, PartialEq, EnumIter, Display)]
160    #[repr(u8)]
161    pub(super) enum DbColumn {
162        GraphDagCborBlake2b256,
163        GraphFull,
164        Settings,
165        EthMappings,
166    }
167
168    impl DbColumn {
169        fn create_column_options(compression: CompressionType) -> Vec<parity_db::ColumnOptions> {
170            DbColumn::iter()
171                .map(|col| {
172                    match col {
173                        DbColumn::GraphDagCborBlake2b256 => parity_db::ColumnOptions {
174                            preimage: true,
175                            compression,
176                            ..Default::default()
177                        },
178                        DbColumn::GraphFull => parity_db::ColumnOptions {
179                            preimage: true,
180                            // This is needed for key retrieval.
181                            btree_index: true,
182                            compression,
183                            ..Default::default()
184                        },
185                        DbColumn::Settings => parity_db::ColumnOptions {
186                            // explicitly disable preimage for settings column
187                            // othewise we are not able to overwrite entries
188                            preimage: false,
189                            // This is needed for key retrieval.
190                            btree_index: true,
191                            compression,
192                            ..Default::default()
193                        },
194                        DbColumn::EthMappings => parity_db::ColumnOptions {
195                            preimage: false,
196                            btree_index: false,
197                            compression,
198                            ..Default::default()
199                        },
200                    }
201                })
202                .collect()
203        }
204    }
205
206    pub(super) struct ParityDb {
207        pub db: parity_db::Db,
208    }
209
210    impl ParityDb {
211        pub(super) fn to_options(path: PathBuf) -> Options {
212            Options {
213                path,
214                sync_wal: true,
215                sync_data: true,
216                stats: false,
217                salt: None,
218                columns: DbColumn::create_column_options(CompressionType::Lz4),
219                compression_threshold: [(0, 128)].into_iter().collect(),
220            }
221        }
222
223        pub(super) fn open(path: impl Into<PathBuf>) -> anyhow::Result<Self> {
224            let opts = Self::to_options(path.into());
225            Ok(Self {
226                db: Db::open_or_create(&opts)?,
227            })
228        }
229    }
230}
231
232/// Database settings from Forest `v0.22.1`
233mod paritydb_0_22_1 {
234    use parity_db::{CompressionType, Db, Options};
235    use std::path::PathBuf;
236    use strum::{Display, EnumIter, IntoEnumIterator};
237
238    #[derive(Copy, Clone, Debug, PartialEq, EnumIter, Display)]
239    #[repr(u8)]
240    pub(super) enum DbColumn {
241        GraphDagCborBlake2b256,
242        GraphFull,
243        Settings,
244        EthMappings,
245        PersistentGraph,
246    }
247
248    impl DbColumn {
249        fn create_column_options(compression: CompressionType) -> Vec<parity_db::ColumnOptions> {
250            DbColumn::iter()
251                .map(|col| {
252                    match col {
253                        DbColumn::GraphDagCborBlake2b256 | DbColumn::PersistentGraph => {
254                            parity_db::ColumnOptions {
255                                preimage: true,
256                                compression,
257                                ..Default::default()
258                            }
259                        }
260                        DbColumn::GraphFull => parity_db::ColumnOptions {
261                            preimage: true,
262                            // This is needed for key retrieval.
263                            btree_index: true,
264                            compression,
265                            ..Default::default()
266                        },
267                        DbColumn::Settings => {
268                            parity_db::ColumnOptions {
269                                // explicitly disable preimage for settings column
270                                // othewise we are not able to overwrite entries
271                                preimage: false,
272                                // This is needed for key retrieval.
273                                btree_index: true,
274                                compression,
275                                ..Default::default()
276                            }
277                        }
278                        DbColumn::EthMappings => parity_db::ColumnOptions {
279                            preimage: false,
280                            btree_index: false,
281                            compression,
282                            ..Default::default()
283                        },
284                    }
285                })
286                .collect()
287        }
288    }
289
290    pub(super) struct ParityDb {
291        pub db: parity_db::Db,
292    }
293
294    impl ParityDb {
295        pub(super) fn to_options(path: PathBuf) -> Options {
296            Options {
297                path,
298                sync_wal: true,
299                sync_data: true,
300                stats: false,
301                salt: None,
302                columns: DbColumn::create_column_options(CompressionType::Lz4),
303                compression_threshold: [(0, 128)].into_iter().collect(),
304            }
305        }
306
307        pub(super) fn open(path: impl Into<PathBuf>) -> anyhow::Result<Self> {
308            let opts = Self::to_options(path.into());
309            Ok(Self {
310                db: Db::open_or_create(&opts)?,
311            })
312        }
313    }
314}