referrerpolicy=no-referrer-when-downgrade

sc_client_db/
upgrade.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Database upgrade logic.
20
21use 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
32/// Version file name.
33const VERSION_FILE_NAME: &str = "db_version";
34
35/// Current db version.
36const CURRENT_VERSION: u32 = 4;
37
38/// Number of columns in v1.
39const V1_NUM_COLUMNS: u32 = 11;
40const V2_NUM_COLUMNS: u32 = 12;
41const V3_NUM_COLUMNS: u32 = 12;
42
43/// Database upgrade errors.
44#[derive(Debug)]
45pub enum UpgradeError {
46	/// Database version cannot be read from existing db_version file.
47	UnknownDatabaseVersion,
48	/// Missing database version file.
49	MissingDatabaseVersionFile,
50	/// Database version no longer supported.
51	UnsupportedVersion(u32),
52	/// Database version comes from future version of the client.
53	FutureDatabaseVersion(u32),
54	/// Invalid justification block.
55	DecodingJustificationBlock,
56	/// Common io error.
57	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
89/// Upgrade database to current version.
90pub 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
113/// Migration from version1 to version2:
114/// 1) the number of columns has changed from 11 to 12;
115/// 2) transactions column is added;
116fn 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
122/// Migration from version2 to version3:
123/// - The format of the stored Justification changed to support multiple Justifications.
124fn 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	// Get all the keys we need to update
129	let keys: Vec<_> = db
130		.iter(columns::JUSTIFICATIONS)
131		.map(|r| r.map(|e| e.0))
132		.collect::<Result<_, _>>()?;
133
134	// Read and update each entry
135	let mut transaction = db.transaction();
136	for key in keys {
137		if let Some(justification) = db.get(columns::JUSTIFICATIONS, &key)? {
138			// Tag each justification with the hardcoded ID for GRANDPA to avoid the dependency on
139			// the GRANDPA crate.
140			// NOTE: when storing justifications the previous API would get a `Vec<u8>` and still
141			// call encode on it.
142			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
153/// Migration from version3 to version4:
154/// 1) the number of columns has changed from 12 to 13;
155/// 2) BODY_INDEX column is added;
156fn 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
162/// Reads current database version from the file at given path.
163/// If the file does not exist returns 0.
164fn 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
177/// Writes current database version to the file.
178/// Creates a new file if the version file does not exist yet.
179pub 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
186/// Returns the version file path.
187fn 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}