referrerpolicy=no-referrer-when-downgrade

polkadot_service/parachains_db/
upgrade.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14//! Migration code for the parachain's DB.
15
16#![cfg(feature = "full-node")]
17
18use super::{columns, other_io_error, DatabaseKind, LOG_TARGET};
19use std::{
20	fs, io,
21	path::{Path, PathBuf},
22	str::FromStr,
23	sync::Arc,
24};
25
26use polkadot_node_core_approval_voting::approval_db::{
27	common::{Config as ApprovalDbConfig, Result as ApprovalDbResult},
28	v2::migration_helpers::v1_to_latest,
29	v3::migration_helpers::v2_to_latest,
30};
31use polkadot_node_subsystem_util::database::{
32	kvdb_impl::DbAdapter as RocksDbAdapter, paritydb_impl::DbAdapter as ParityDbAdapter, Database,
33};
34type Version = u32;
35
36/// Version file name.
37const VERSION_FILE_NAME: &'static str = "parachain_db_version";
38
39/// Current db version.
40/// Version 4 changes approval db format for `OurAssignment`.
41/// Version 5 changes approval db format to hold some additional
42/// information about delayed approvals.
43pub(crate) const CURRENT_VERSION: Version = 5;
44
45#[derive(thiserror::Error, Debug)]
46pub enum Error {
47	#[error("I/O error when reading/writing the version")]
48	Io(#[from] io::Error),
49	#[error("The version file format is incorrect")]
50	CorruptedVersionFile,
51	#[error("Parachains DB has a future version (expected {current:?}, found {got:?})")]
52	FutureVersion { current: Version, got: Version },
53	#[error("Parachain DB migration failed")]
54	MigrationFailed,
55	#[error("Parachain DB migration would take forever")]
56	MigrationLoop,
57}
58
59impl From<Error> for io::Error {
60	fn from(me: Error) -> io::Error {
61		match me {
62			Error::Io(e) => e,
63			_ => super::other_io_error(me.to_string()),
64		}
65	}
66}
67
68/// Try upgrading parachain's database to a target version.
69pub(crate) fn try_upgrade_db(
70	db_path: &Path,
71	db_kind: DatabaseKind,
72	target_version: Version,
73) -> Result<(), Error> {
74	// Ensure we don't loop forever below because of a bug.
75	const MAX_MIGRATIONS: u32 = 30;
76
77	#[cfg(test)]
78	remove_file_lock(&db_path);
79
80	// Loop migrations until we reach the target version.
81	for _ in 0..MAX_MIGRATIONS {
82		let version = try_upgrade_db_to_next_version(db_path, db_kind)?;
83
84		#[cfg(test)]
85		remove_file_lock(&db_path);
86
87		if version == target_version {
88			return Ok(())
89		}
90	}
91
92	Err(Error::MigrationLoop)
93}
94
95/// Try upgrading parachain's database to the next version.
96/// If successful, it returns the current version.
97pub(crate) fn try_upgrade_db_to_next_version(
98	db_path: &Path,
99	db_kind: DatabaseKind,
100) -> Result<Version, Error> {
101	let is_empty = db_path.read_dir().map_or(true, |mut d| d.next().is_none());
102
103	let new_version = if !is_empty {
104		match get_db_version(db_path)? {
105			// 0 -> 1 migration
106			Some(0) => migrate_from_version_0_to_1(db_path, db_kind)?,
107			// 1 -> 2 migration
108			Some(1) => migrate_from_version_1_to_2(db_path, db_kind)?,
109			// 2 -> 3 migration
110			Some(2) => migrate_from_version_2_to_3(db_path, db_kind)?,
111			// 3 -> 4 migration
112			Some(3) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v1_to_latest)?,
113			Some(4) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v2_to_latest)?,
114			// Already at current version, do nothing.
115			Some(CURRENT_VERSION) => CURRENT_VERSION,
116			// This is an arbitrary future version, we don't handle it.
117			Some(v) => return Err(Error::FutureVersion { current: CURRENT_VERSION, got: v }),
118			// No version file. For `RocksDB` we don't need to do anything.
119			None if db_kind == DatabaseKind::RocksDB => CURRENT_VERSION,
120			// No version file. `ParityDB` did not previously have a version defined.
121			// We handle this as a `0 -> 1` migration.
122			None if db_kind == DatabaseKind::ParityDB =>
123				migrate_from_version_0_to_1(db_path, db_kind)?,
124			None => unreachable!(),
125		}
126	} else {
127		CURRENT_VERSION
128	};
129
130	update_version(db_path, new_version)?;
131	Ok(new_version)
132}
133
134/// Reads current database version from the file at given path.
135/// If the file does not exist returns `None`, otherwise the version stored in the file.
136fn get_db_version(path: &Path) -> Result<Option<Version>, Error> {
137	match fs::read_to_string(version_file_path(path)) {
138		Err(ref err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
139		Err(err) => Err(err.into()),
140		Ok(content) => u32::from_str(&content)
141			.map(|v| Some(v))
142			.map_err(|_| Error::CorruptedVersionFile),
143	}
144}
145
146/// Writes current database version to the file.
147/// Creates a new file if the version file does not exist yet.
148fn update_version(path: &Path, new_version: Version) -> Result<(), Error> {
149	fs::create_dir_all(path)?;
150	fs::write(version_file_path(path), new_version.to_string()).map_err(Into::into)
151}
152
153/// Returns the version file path.
154fn version_file_path(path: &Path) -> PathBuf {
155	let mut file_path = path.to_owned();
156	file_path.push(VERSION_FILE_NAME);
157	file_path
158}
159
160fn migrate_from_version_0_to_1(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
161	gum::info!(target: LOG_TARGET, "Migrating parachains db from version 0 to version 1 ...");
162
163	match db_kind {
164		DatabaseKind::ParityDB => paritydb_migrate_from_version_0_to_1(path),
165		DatabaseKind::RocksDB => rocksdb_migrate_from_version_0_to_1(path),
166	}
167	.and_then(|result| {
168		gum::info!(target: LOG_TARGET, "Migration complete! ");
169		Ok(result)
170	})
171}
172
173fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
174	gum::info!(target: LOG_TARGET, "Migrating parachains db from version 1 to version 2 ...");
175
176	match db_kind {
177		DatabaseKind::ParityDB => paritydb_migrate_from_version_1_to_2(path),
178		DatabaseKind::RocksDB => rocksdb_migrate_from_version_1_to_2(path),
179	}
180	.and_then(|result| {
181		gum::info!(target: LOG_TARGET, "Migration complete! ");
182		Ok(result)
183	})
184}
185
186// Migrate approval voting database.
187// In 4  `OurAssignment` has been changed to support the v2 assignments.
188// In 5, `BlockEntry` has been changed to store the number of delayed approvals.
189// As these are backwards compatible, we'll convert the old entries in the new format.
190fn migrate_from_version_3_or_4_to_5<F>(
191	path: &Path,
192	db_kind: DatabaseKind,
193	migration_function: F,
194) -> Result<Version, Error>
195where
196	F: Fn(Arc<dyn Database>, ApprovalDbConfig) -> ApprovalDbResult<()>,
197{
198	gum::info!(target: LOG_TARGET, "Migrating parachains db from version 3 to version 4 ...");
199
200	let approval_db_config =
201		ApprovalDbConfig { col_approval_data: super::REAL_COLUMNS.col_approval_data };
202
203	let _result = match db_kind {
204		DatabaseKind::ParityDB => {
205			let db = ParityDbAdapter::new(
206				parity_db::Db::open(&paritydb_version_3_config(path))
207					.map_err(|e| other_io_error(format!("Error opening db {:?}", e)))?,
208				super::columns::v3::ORDERED_COL,
209			);
210
211			migration_function(Arc::new(db), approval_db_config)
212				.map_err(|_| Error::MigrationFailed)?;
213		},
214		DatabaseKind::RocksDB => {
215			let db_path = path
216				.to_str()
217				.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
218			let db_cfg =
219				kvdb_rocksdb::DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
220			let db = RocksDbAdapter::new(
221				kvdb_rocksdb::Database::open(&db_cfg, db_path)?,
222				&super::columns::v3::ORDERED_COL,
223			);
224
225			migration_function(Arc::new(db), approval_db_config)
226				.map_err(|_| Error::MigrationFailed)?;
227		},
228	};
229
230	gum::info!(target: LOG_TARGET, "Migration complete! ");
231	Ok(CURRENT_VERSION)
232}
233
234fn migrate_from_version_2_to_3(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
235	gum::info!(target: LOG_TARGET, "Migrating parachains db from version 2 to version 3 ...");
236	match db_kind {
237		DatabaseKind::ParityDB => paritydb_migrate_from_version_2_to_3(path),
238		DatabaseKind::RocksDB => rocksdb_migrate_from_version_2_to_3(path),
239	}
240	.and_then(|result| {
241		gum::info!(target: LOG_TARGET, "Migration complete! ");
242		Ok(result)
243	})
244}
245
246/// Migration from version 0 to version 1:
247/// * the number of columns has changed from 3 to 5;
248fn rocksdb_migrate_from_version_0_to_1(path: &Path) -> Result<Version, Error> {
249	use kvdb_rocksdb::{Database, DatabaseConfig};
250
251	let db_path = path
252		.to_str()
253		.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
254	let db_cfg = DatabaseConfig::with_columns(super::columns::v0::NUM_COLUMNS);
255	let mut db = Database::open(&db_cfg, db_path)?;
256
257	db.add_column()?;
258	db.add_column()?;
259
260	Ok(1)
261}
262
263/// Migration from version 1 to version 2:
264/// * the number of columns has changed from 5 to 6;
265fn rocksdb_migrate_from_version_1_to_2(path: &Path) -> Result<Version, Error> {
266	use kvdb_rocksdb::{Database, DatabaseConfig};
267
268	let db_path = path
269		.to_str()
270		.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
271	let db_cfg = DatabaseConfig::with_columns(super::columns::v1::NUM_COLUMNS);
272	let mut db = Database::open(&db_cfg, db_path)?;
273
274	db.add_column()?;
275
276	Ok(2)
277}
278
279fn rocksdb_migrate_from_version_2_to_3(path: &Path) -> Result<Version, Error> {
280	use kvdb_rocksdb::{Database, DatabaseConfig};
281
282	let db_path = path
283		.to_str()
284		.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
285	let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS);
286	let mut db = Database::open(&db_cfg, db_path)?;
287
288	db.remove_last_column()?;
289
290	Ok(3)
291}
292
293// This currently clears columns which had their configs altered between versions.
294// The columns to be changed are constrained by the `allowed_columns` vector.
295fn paritydb_fix_columns(
296	path: &Path,
297	options: parity_db::Options,
298	allowed_columns: Vec<u32>,
299) -> io::Result<()> {
300	// Figure out which columns to delete. This will be determined by inspecting
301	// the metadata file.
302	if let Some(metadata) = parity_db::Options::load_metadata(&path)
303		.map_err(|e| other_io_error(format!("Error reading metadata {:?}", e)))?
304	{
305		let columns_to_clear = metadata
306			.columns
307			.into_iter()
308			.enumerate()
309			.filter(|(idx, _)| allowed_columns.contains(&(*idx as u32)))
310			.filter_map(|(idx, opts)| {
311				let changed = opts != options.columns[idx];
312				if changed {
313					gum::debug!(
314						target: LOG_TARGET,
315						"Column {} will be cleared. Old options: {:?}, New options: {:?}",
316						idx,
317						opts,
318						options.columns[idx]
319					);
320					Some(idx)
321				} else {
322					None
323				}
324			})
325			.collect::<Vec<_>>();
326
327		if columns_to_clear.len() > 0 {
328			gum::debug!(
329				target: LOG_TARGET,
330				"Database column changes detected, need to cleanup {} columns.",
331				columns_to_clear.len()
332			);
333		}
334
335		for column in columns_to_clear {
336			gum::debug!(target: LOG_TARGET, "Clearing column {}", column,);
337			parity_db::clear_column(path, column.try_into().expect("Invalid column ID"))
338				.map_err(|e| other_io_error(format!("Error clearing column {:?}", e)))?;
339		}
340
341		// Write the updated column options.
342		options
343			.write_metadata(path, &metadata.salt)
344			.map_err(|e| other_io_error(format!("Error writing metadata {:?}", e)))?;
345	}
346
347	Ok(())
348}
349
350/// Database configuration for version 1.
351pub(crate) fn paritydb_version_1_config(path: &Path) -> parity_db::Options {
352	let mut options =
353		parity_db::Options::with_columns(&path, super::columns::v1::NUM_COLUMNS as u8);
354	for i in columns::v4::ORDERED_COL {
355		options.columns[*i as usize].btree_index = true;
356	}
357
358	options
359}
360
361/// Database configuration for version 2.
362pub(crate) fn paritydb_version_2_config(path: &Path) -> parity_db::Options {
363	let mut options =
364		parity_db::Options::with_columns(&path, super::columns::v2::NUM_COLUMNS as u8);
365	for i in columns::v4::ORDERED_COL {
366		options.columns[*i as usize].btree_index = true;
367	}
368
369	options
370}
371
372/// Database configuration for version 3.
373pub(crate) fn paritydb_version_3_config(path: &Path) -> parity_db::Options {
374	let mut options =
375		parity_db::Options::with_columns(&path, super::columns::v3::NUM_COLUMNS as u8);
376	for i in columns::v3::ORDERED_COL {
377		options.columns[*i as usize].btree_index = true;
378	}
379
380	options
381}
382
383/// Database configuration for version 0. This is useful just for testing.
384#[cfg(test)]
385pub(crate) fn paritydb_version_0_config(path: &Path) -> parity_db::Options {
386	let mut options =
387		parity_db::Options::with_columns(&path, super::columns::v0::NUM_COLUMNS as u8);
388	options.columns[super::columns::v4::COL_AVAILABILITY_META as usize].btree_index = true;
389
390	options
391}
392
393/// Migration from version 0 to version 1.
394/// Cases covered:
395/// - upgrading from v0.9.23 or earlier -> the `dispute coordinator column` was changed
396/// - upgrading from v0.9.24+ -> this is a no op assuming the DB has been manually fixed as per
397/// release notes
398fn paritydb_migrate_from_version_0_to_1(path: &Path) -> Result<Version, Error> {
399	// Delete the `dispute coordinator` column if needed (if column configuration is changed).
400	paritydb_fix_columns(
401		path,
402		paritydb_version_1_config(path),
403		vec![super::columns::v4::COL_DISPUTE_COORDINATOR_DATA],
404	)?;
405
406	Ok(1)
407}
408
409/// Migration from version 1 to version 2:
410/// - add a new column for session information storage
411fn paritydb_migrate_from_version_1_to_2(path: &Path) -> Result<Version, Error> {
412	let mut options = paritydb_version_1_config(path);
413
414	// Adds the session info column.
415	parity_db::Db::add_column(&mut options, Default::default())
416		.map_err(|e| other_io_error(format!("Error adding column {:?}", e)))?;
417
418	Ok(2)
419}
420
421/// Migration from version 2 to version 3:
422/// - drop the column used by `RollingSessionWindow`
423fn paritydb_migrate_from_version_2_to_3(path: &Path) -> Result<Version, Error> {
424	parity_db::Db::drop_last_column(&mut paritydb_version_2_config(path))
425		.map_err(|e| other_io_error(format!("Error removing COL_SESSION_WINDOW_DATA {:?}", e)))?;
426	Ok(3)
427}
428
429/// Remove the lock file. If file is locked, it will wait up to 1s.
430#[cfg(test)]
431pub fn remove_file_lock(path: &std::path::Path) {
432	use std::{io::ErrorKind, thread::sleep, time::Duration};
433
434	let mut lock_path = std::path::PathBuf::from(path);
435	lock_path.push("lock");
436
437	for _ in 0..10 {
438		let result = std::fs::remove_file(lock_path.as_path());
439		match result {
440			Err(error) => match error.kind() {
441				ErrorKind::WouldBlock => {
442					sleep(Duration::from_millis(100));
443					continue
444				},
445				_ => return,
446			},
447			Ok(_) => {},
448		}
449	}
450
451	unreachable!("Database is locked, waited 1s for lock file: {:?}", lock_path);
452}
453
454#[cfg(test)]
455mod tests {
456	use super::{
457		columns::{v2::COL_SESSION_WINDOW_DATA, v4::*},
458		*,
459	};
460	use kvdb_rocksdb::{Database, DatabaseConfig};
461	use polkadot_node_core_approval_voting::approval_db::{
462		v2::migration_helpers::v1_fill_test_data,
463		v3::migration_helpers::{v1_to_latest_sanity_check, v2_fill_test_data},
464	};
465	use polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter;
466	use polkadot_primitives_test_helpers::dummy_candidate_receipt_v2;
467
468	#[test]
469	fn test_paritydb_migrate_0_to_1() {
470		use parity_db::Db;
471
472		let db_dir = tempfile::tempdir().unwrap();
473		let path = db_dir.path();
474		{
475			let db = Db::open_or_create(&paritydb_version_0_config(&path)).unwrap();
476
477			db.commit(vec![(
478				COL_AVAILABILITY_META as u8,
479				b"5678".to_vec(),
480				Some(b"somevalue".to_vec()),
481			)])
482			.unwrap();
483		}
484
485		try_upgrade_db(&path, DatabaseKind::ParityDB, 1).unwrap();
486
487		let db = Db::open(&paritydb_version_1_config(&path)).unwrap();
488		assert_eq!(
489			db.get(COL_AVAILABILITY_META as u8, b"5678").unwrap(),
490			Some("somevalue".as_bytes().to_vec())
491		);
492	}
493
494	#[test]
495	fn test_paritydb_migrate_1_to_2() {
496		use parity_db::Db;
497
498		let db_dir = tempfile::tempdir().unwrap();
499		let path = db_dir.path();
500
501		// We need to properly set db version for upgrade to work.
502		fs::write(version_file_path(path), "1").expect("Failed to write DB version");
503
504		{
505			let db = Db::open_or_create(&paritydb_version_1_config(&path)).unwrap();
506
507			// Write some dummy data
508			db.commit(vec![(
509				COL_DISPUTE_COORDINATOR_DATA as u8,
510				b"1234".to_vec(),
511				Some(b"somevalue".to_vec()),
512			)])
513			.unwrap();
514
515			assert_eq!(db.num_columns(), columns::v1::NUM_COLUMNS as u8);
516		}
517
518		try_upgrade_db(&path, DatabaseKind::ParityDB, 2).unwrap();
519
520		let db = Db::open(&paritydb_version_2_config(&path)).unwrap();
521
522		assert_eq!(db.num_columns(), columns::v2::NUM_COLUMNS as u8);
523
524		assert_eq!(
525			db.get(COL_DISPUTE_COORDINATOR_DATA as u8, b"1234").unwrap(),
526			Some("somevalue".as_bytes().to_vec())
527		);
528
529		// Test we can write the new column.
530		db.commit(vec![(
531			COL_SESSION_WINDOW_DATA as u8,
532			b"1337".to_vec(),
533			Some(b"0xdeadb00b".to_vec()),
534		)])
535		.unwrap();
536
537		// Read back data from new column.
538		assert_eq!(
539			db.get(COL_SESSION_WINDOW_DATA as u8, b"1337").unwrap(),
540			Some("0xdeadb00b".as_bytes().to_vec())
541		);
542	}
543
544	#[test]
545	fn test_rocksdb_migrate_1_to_2() {
546		use kvdb::{DBKey, DBOp};
547		use kvdb_rocksdb::{Database, DatabaseConfig};
548		use polkadot_node_subsystem_util::database::{
549			kvdb_impl::DbAdapter, DBTransaction, KeyValueDB,
550		};
551
552		let db_dir = tempfile::tempdir().unwrap();
553		let db_path = db_dir.path().to_str().unwrap();
554		let db_cfg = DatabaseConfig::with_columns(super::columns::v1::NUM_COLUMNS);
555		let db = Database::open(&db_cfg, db_path).unwrap();
556		assert_eq!(db.num_columns(), super::columns::v1::NUM_COLUMNS as u32);
557
558		// We need to properly set db version for upgrade to work.
559		fs::write(version_file_path(db_dir.path()), "1").expect("Failed to write DB version");
560		{
561			let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
562			db.write(DBTransaction {
563				ops: vec![DBOp::Insert {
564					col: COL_DISPUTE_COORDINATOR_DATA,
565					key: DBKey::from_slice(b"1234"),
566					value: b"0xdeadb00b".to_vec(),
567				}],
568			})
569			.unwrap();
570		}
571
572		try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 2).unwrap();
573
574		let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS);
575		let db = Database::open(&db_cfg, db_path).unwrap();
576
577		assert_eq!(db.num_columns(), super::columns::v2::NUM_COLUMNS);
578
579		let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
580
581		assert_eq!(
582			db.get(COL_DISPUTE_COORDINATOR_DATA, b"1234").unwrap(),
583			Some("0xdeadb00b".as_bytes().to_vec())
584		);
585
586		// Test we can write the new column.
587		db.write(DBTransaction {
588			ops: vec![DBOp::Insert {
589				col: COL_SESSION_WINDOW_DATA,
590				key: DBKey::from_slice(b"1337"),
591				value: b"0xdeadb00b".to_vec(),
592			}],
593		})
594		.unwrap();
595
596		// Read back data from new column.
597		assert_eq!(
598			db.get(COL_SESSION_WINDOW_DATA, b"1337").unwrap(),
599			Some("0xdeadb00b".as_bytes().to_vec())
600		);
601	}
602
603	#[test]
604	fn test_migrate_3_to_5() {
605		let db_dir = tempfile::tempdir().unwrap();
606		let db_path = db_dir.path().to_str().unwrap();
607		let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
608
609		let approval_cfg = ApprovalDbConfig {
610			col_approval_data: crate::parachains_db::REAL_COLUMNS.col_approval_data,
611		};
612
613		// We need to properly set db version for upgrade to work.
614		fs::write(version_file_path(db_dir.path()), "3").expect("Failed to write DB version");
615		let expected_candidates = {
616			let db = Database::open(&db_cfg, db_path).unwrap();
617			assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
618			let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
619			// Fill the approval voting column with test data.
620			v1_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt_v2)
621				.unwrap()
622		};
623
624		try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
625
626		let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
627		let db = Database::open(&db_cfg, db_path).unwrap();
628		let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
629
630		v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates)
631			.unwrap();
632	}
633
634	#[test]
635	fn test_migrate_4_to_5() {
636		let db_dir = tempfile::tempdir().unwrap();
637		let db_path = db_dir.path().to_str().unwrap();
638		let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
639
640		let approval_cfg = ApprovalDbConfig {
641			col_approval_data: crate::parachains_db::REAL_COLUMNS.col_approval_data,
642		};
643
644		// We need to properly set db version for upgrade to work.
645		fs::write(version_file_path(db_dir.path()), "4").expect("Failed to write DB version");
646		let expected_candidates = {
647			let db = Database::open(&db_cfg, db_path).unwrap();
648			assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
649			let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
650			// Fill the approval voting column with test data.
651			v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt_v2)
652				.unwrap()
653		};
654
655		try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
656
657		let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
658		let db = Database::open(&db_cfg, db_path).unwrap();
659		let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
660
661		v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates)
662			.unwrap();
663	}
664
665	#[test]
666	fn test_rocksdb_migrate_0_to_5() {
667		use kvdb_rocksdb::{Database, DatabaseConfig};
668
669		let db_dir = tempfile::tempdir().unwrap();
670		let db_path = db_dir.path().to_str().unwrap();
671
672		fs::write(version_file_path(db_dir.path()), "0").expect("Failed to write DB version");
673		try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
674
675		let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
676		let db = Database::open(&db_cfg, db_path).unwrap();
677
678		assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS);
679	}
680
681	#[test]
682	fn test_paritydb_migrate_0_to_5() {
683		use parity_db::Db;
684
685		let db_dir = tempfile::tempdir().unwrap();
686		let path = db_dir.path();
687
688		// We need to properly set db version for upgrade to work.
689		fs::write(version_file_path(path), "0").expect("Failed to write DB version");
690
691		{
692			let db = Db::open_or_create(&paritydb_version_0_config(&path)).unwrap();
693			assert_eq!(db.num_columns(), columns::v0::NUM_COLUMNS as u8);
694		}
695
696		try_upgrade_db(&path, DatabaseKind::ParityDB, 5).unwrap();
697
698		let db = Db::open(&paritydb_version_3_config(&path)).unwrap();
699		assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS as u8);
700	}
701
702	#[test]
703	fn test_paritydb_migrate_2_to_3() {
704		use parity_db::Db;
705
706		let db_dir = tempfile::tempdir().unwrap();
707		let path = db_dir.path();
708		let test_key = b"1337";
709
710		// We need to properly set db version for upgrade to work.
711		fs::write(version_file_path(path), "2").expect("Failed to write DB version");
712
713		{
714			let db = Db::open_or_create(&paritydb_version_2_config(&path)).unwrap();
715
716			// Write some dummy data
717			db.commit(vec![(
718				COL_SESSION_WINDOW_DATA as u8,
719				test_key.to_vec(),
720				Some(b"0xdeadb00b".to_vec()),
721			)])
722			.unwrap();
723
724			assert_eq!(db.num_columns(), columns::v2::NUM_COLUMNS as u8);
725		}
726
727		try_upgrade_db(&path, DatabaseKind::ParityDB, 3).unwrap();
728
729		let db = Db::open(&paritydb_version_3_config(&path)).unwrap();
730
731		assert_eq!(db.num_columns(), columns::v3::NUM_COLUMNS as u8);
732	}
733
734	#[test]
735	fn test_rocksdb_migrate_2_to_3() {
736		use kvdb_rocksdb::{Database, DatabaseConfig};
737
738		let db_dir = tempfile::tempdir().unwrap();
739		let db_path = db_dir.path().to_str().unwrap();
740		let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS);
741
742		{
743			let db = Database::open(&db_cfg, db_path).unwrap();
744			assert_eq!(db.num_columns(), super::columns::v2::NUM_COLUMNS as u32);
745		}
746
747		// We need to properly set db version for upgrade to work.
748		fs::write(version_file_path(db_dir.path()), "2").expect("Failed to write DB version");
749
750		try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 3).unwrap();
751
752		let db_cfg = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
753		let db = Database::open(&db_cfg, db_path).unwrap();
754
755		assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS);
756	}
757}