forest/db/migration/
v0_26_0.rs

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