1use std::{
22 fmt, fs,
23 io::{self, ErrorKind, Read, Write},
24 path::{Path, PathBuf},
25};
26
27use crate::{columns, utils::DatabaseType};
28use codec::{Decode, Encode};
29use kvdb_rocksdb::{Database, DatabaseConfig};
30use sp_runtime::traits::Block as BlockT;
31
32const VERSION_FILE_NAME: &str = "db_version";
34
35const CURRENT_VERSION: u32 = 4;
37
38const V1_NUM_COLUMNS: u32 = 11;
40const V2_NUM_COLUMNS: u32 = 12;
41const V3_NUM_COLUMNS: u32 = 12;
42
43#[derive(Debug)]
45pub enum UpgradeError {
46 UnknownDatabaseVersion,
48 MissingDatabaseVersionFile,
50 UnsupportedVersion(u32),
52 FutureDatabaseVersion(u32),
54 DecodingJustificationBlock,
56 Io(io::Error),
58}
59
60pub type UpgradeResult<T> = Result<T, UpgradeError>;
61
62impl From<io::Error> for UpgradeError {
63 fn from(err: io::Error) -> Self {
64 UpgradeError::Io(err)
65 }
66}
67
68impl fmt::Display for UpgradeError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 UpgradeError::UnknownDatabaseVersion => {
72 write!(f, "Database version cannot be read from existing db_version file")
73 },
74 UpgradeError::MissingDatabaseVersionFile => write!(f, "Missing database version file"),
75 UpgradeError::UnsupportedVersion(version) => {
76 write!(f, "Database version no longer supported: {}", version)
77 },
78 UpgradeError::FutureDatabaseVersion(version) => {
79 write!(f, "Database version comes from future version of the client: {}", version)
80 },
81 UpgradeError::DecodingJustificationBlock => {
82 write!(f, "Decoding justification block failed")
83 },
84 UpgradeError::Io(err) => write!(f, "Io error: {}", err),
85 }
86 }
87}
88
89pub fn upgrade_db<Block: BlockT>(db_path: &Path, db_type: DatabaseType) -> UpgradeResult<()> {
91 let db_version = current_version(db_path)?;
92 match db_version {
93 0 => return Err(UpgradeError::UnsupportedVersion(db_version)),
94 1 => {
95 migrate_1_to_2::<Block>(db_path, db_type)?;
96 migrate_2_to_3::<Block>(db_path, db_type)?;
97 migrate_3_to_4::<Block>(db_path, db_type)?;
98 },
99 2 => {
100 migrate_2_to_3::<Block>(db_path, db_type)?;
101 migrate_3_to_4::<Block>(db_path, db_type)?;
102 },
103 3 => {
104 migrate_3_to_4::<Block>(db_path, db_type)?;
105 },
106 CURRENT_VERSION => (),
107 _ => return Err(UpgradeError::FutureDatabaseVersion(db_version)),
108 }
109 update_version(db_path)?;
110 Ok(())
111}
112
113fn migrate_1_to_2<Block: BlockT>(db_path: &Path, _db_type: DatabaseType) -> UpgradeResult<()> {
117 let db_cfg = DatabaseConfig::with_columns(V1_NUM_COLUMNS);
118 let mut db = Database::open(&db_cfg, db_path)?;
119 db.add_column().map_err(Into::into)
120}
121
122fn migrate_2_to_3<Block: BlockT>(db_path: &Path, _db_type: DatabaseType) -> UpgradeResult<()> {
125 let db_cfg = DatabaseConfig::with_columns(V2_NUM_COLUMNS);
126 let db = Database::open(&db_cfg, db_path)?;
127
128 let keys: Vec<_> = db
130 .iter(columns::JUSTIFICATIONS)
131 .map(|r| r.map(|e| e.0))
132 .collect::<Result<_, _>>()?;
133
134 let mut transaction = db.transaction();
136 for key in keys {
137 if let Some(justification) = db.get(columns::JUSTIFICATIONS, &key)? {
138 let justification = Vec::<u8>::decode(&mut &justification[..])
143 .map_err(|_| UpgradeError::DecodingJustificationBlock)?;
144 let justifications = sp_runtime::Justifications::from((*b"FRNK", justification));
145 transaction.put_vec(columns::JUSTIFICATIONS, &key, justifications.encode());
146 }
147 }
148 db.write(transaction)?;
149
150 Ok(())
151}
152
153fn migrate_3_to_4<Block: BlockT>(db_path: &Path, _db_type: DatabaseType) -> UpgradeResult<()> {
157 let db_cfg = DatabaseConfig::with_columns(V3_NUM_COLUMNS);
158 let mut db = Database::open(&db_cfg, db_path)?;
159 db.add_column().map_err(Into::into)
160}
161
162fn current_version(path: &Path) -> UpgradeResult<u32> {
165 match fs::File::open(version_file_path(path)) {
166 Err(ref err) if err.kind() == ErrorKind::NotFound =>
167 Err(UpgradeError::MissingDatabaseVersionFile),
168 Err(_) => Err(UpgradeError::UnknownDatabaseVersion),
169 Ok(mut file) => {
170 let mut s = String::new();
171 file.read_to_string(&mut s).map_err(|_| UpgradeError::UnknownDatabaseVersion)?;
172 u32::from_str_radix(&s, 10).map_err(|_| UpgradeError::UnknownDatabaseVersion)
173 },
174 }
175}
176
177pub fn update_version(path: &Path) -> io::Result<()> {
180 fs::create_dir_all(path)?;
181 let mut file = fs::File::create(version_file_path(path))?;
182 file.write_all(format!("{}", CURRENT_VERSION).as_bytes())?;
183 Ok(())
184}
185
186fn version_file_path(path: &Path) -> PathBuf {
188 let mut file_path = path.to_owned();
189 file_path.push(VERSION_FILE_NAME);
190 file_path
191}
192
193#[cfg(all(test, feature = "rocksdb"))]
194mod tests {
195 use super::*;
196 use crate::{tests::Block, DatabaseSource};
197
198 fn create_db(db_path: &Path, version: Option<u32>) {
199 if let Some(version) = version {
200 fs::create_dir_all(db_path).unwrap();
201 let mut file = fs::File::create(version_file_path(db_path)).unwrap();
202 file.write_all(format!("{}", version).as_bytes()).unwrap();
203 }
204 }
205
206 fn open_database(db_path: &Path, db_type: DatabaseType) -> sp_blockchain::Result<()> {
207 crate::utils::open_database::<Block>(
208 &DatabaseSource::RocksDb { path: db_path.to_owned(), cache_size: 128 },
209 db_type,
210 true,
211 )
212 .map(|_| ())
213 .map_err(|e| sp_blockchain::Error::Backend(e.to_string()))
214 }
215
216 #[test]
217 fn downgrade_never_happens() {
218 let db_dir = tempfile::TempDir::new().unwrap();
219 create_db(db_dir.path(), Some(CURRENT_VERSION + 1));
220 assert!(open_database(db_dir.path(), DatabaseType::Full).is_err());
221 }
222
223 #[test]
224 fn open_empty_database_works() {
225 let db_type = DatabaseType::Full;
226 let db_dir = tempfile::TempDir::new().unwrap();
227 let db_dir = db_dir.path().join(db_type.as_str());
228 open_database(&db_dir, db_type).unwrap();
229 open_database(&db_dir, db_type).unwrap();
230 assert_eq!(current_version(&db_dir).unwrap(), CURRENT_VERSION);
231 }
232
233 #[test]
234 fn upgrade_to_3_works() {
235 let db_type = DatabaseType::Full;
236 for version_from_file in &[None, Some(1), Some(2)] {
237 let db_dir = tempfile::TempDir::new().unwrap();
238 let db_path = db_dir.path().join(db_type.as_str());
239 create_db(&db_path, *version_from_file);
240 open_database(&db_path, db_type).unwrap();
241 assert_eq!(current_version(&db_path).unwrap(), CURRENT_VERSION);
242 }
243 }
244
245 #[test]
246 fn upgrade_to_4_works() {
247 let db_type = DatabaseType::Full;
248 for version_from_file in &[None, Some(1), Some(2), Some(3)] {
249 let db_dir = tempfile::TempDir::new().unwrap();
250 let db_path = db_dir.path().join(db_type.as_str());
251 create_db(&db_path, *version_from_file);
252 open_database(&db_path, db_type).unwrap();
253 assert_eq!(current_version(&db_path).unwrap(), CURRENT_VERSION);
254 }
255 }
256}