Skip to content

Commit 712ce36

Browse files
committed
fix(consensus-db): tolerate a missing data table during migration
migrate_certificates and migrate_decided_blocks open their table inside a read transaction, which fails with TableDoesNotExist when the table is not there. migrate_undecided_blocks and migrate_pending_parts open theirs inside a write transaction, which creates it, so those two already treat a missing table as an empty one. A datadir can reach the migration before the tables exist. Db::new migrates, and only afterwards does Store::open call create_tables; needs_migration records "file exists, no schema version" as v0 and asks for a migration, and redb makes the file before that version is written. A first start interrupted in between leaves a database that every later start rejects, and `db migrate` rejects it the same way, dry run included. Treat a missing table as empty in both, matching the other two.
1 parent 3f1efe9 commit 712ce36

1 file changed

Lines changed: 54 additions & 16 deletions

File tree

‎crates/consensus-db/src/migrations.rs‎

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,19 @@ impl MigrationCoordinator {
303303

304304
let mut stats = MigrationStats::default();
305305

306-
// start from the min height
307-
let mut next_height = if let Some((min_height, _)) = self
308-
.db
309-
.begin_read()?
310-
.open_table(CERTIFICATES_TABLE)?
311-
.first()?
312-
{
313-
min_height.value()
306+
// Start from the min height. A datadir that never wrote to this table has
307+
// no table yet, and opening it in a read transaction fails outright. The
308+
// undecided-block and pending-part migrations open theirs in a write
309+
// transaction, which creates it, so treat a missing table as an empty one
310+
// rather than aborting the whole migration.
311+
let min_height = match self.db.begin_read()?.open_table(CERTIFICATES_TABLE) {
312+
Ok(table) => table.first()?.map(|(key, _)| key.value()),
313+
Err(redb::TableError::TableDoesNotExist(_)) => None,
314+
Err(e) => return Err(e.into()),
315+
};
316+
317+
let mut next_height = if let Some(min_height) = min_height {
318+
min_height
314319
} else {
315320
stats.tables_migrated += 1;
316321
return Ok(stats);
@@ -395,14 +400,16 @@ impl MigrationCoordinator {
395400

396401
let mut stats = MigrationStats::default();
397402

398-
// start from the min height
399-
let mut next_height = if let Some((min_height, _)) = self
400-
.db
401-
.begin_read()?
402-
.open_table(DECIDED_BLOCKS_TABLE)?
403-
.first()?
404-
{
405-
min_height.value()
403+
// Start from the min height, treating a missing table as empty for the
404+
// same reason as the certificates table above.
405+
let min_height = match self.db.begin_read()?.open_table(DECIDED_BLOCKS_TABLE) {
406+
Ok(table) => table.first()?.map(|(key, _)| key.value()),
407+
Err(redb::TableError::TableDoesNotExist(_)) => None,
408+
Err(e) => return Err(e.into()),
409+
};
410+
411+
let mut next_height = if let Some(min_height) = min_height {
412+
min_height
406413
} else {
407414
stats.tables_migrated += 1;
408415
return Ok(stats);
@@ -1565,4 +1572,35 @@ mod tests {
15651572
.unwrap();
15661573
assert_eq!(value4.value()[0], 1);
15671574
}
1575+
1576+
#[test]
1577+
fn test_migration_when_data_tables_are_missing() {
1578+
// A database file with no data tables: `Db::new` runs the migration before
1579+
// `Store::open` calls `create_tables`, so a first start interrupted between
1580+
// the two leaves exactly this on disk.
1581+
let (db, _path) = create_test_db();
1582+
1583+
let coordinator = MigrationCoordinator::new(db);
1584+
1585+
// An existing file with no schema version is recorded as v0 and migrated.
1586+
assert!(coordinator.needs_migration(true).unwrap());
1587+
1588+
let preview = coordinator
1589+
.preview_migrate()
1590+
.expect("dry run must not fail when the data tables are missing");
1591+
assert_eq!(preview.records_scanned, 0);
1592+
assert_eq!(preview.tables_migrated, 4);
1593+
1594+
let stats = coordinator
1595+
.migrate()
1596+
.expect("migration must not fail when the data tables are missing");
1597+
assert_eq!(stats.records_scanned, 0);
1598+
assert_eq!(stats.records_upgraded, 0);
1599+
assert_eq!(stats.tables_migrated, 4);
1600+
1601+
assert_eq!(
1602+
coordinator.current_schema_version().unwrap(),
1603+
Some(DB_SCHEMA_VERSION)
1604+
);
1605+
}
15681606
}

0 commit comments

Comments
 (0)