Skip to content

Commit c0a1046

Browse files
authored
Add global storage version for data migrations (#3469)
* feat(storage): add global storage versioning and helpers Introduce a global storage version key to the database, enabling versioned migrations for breaking changes in stored data structures. Add helper functions to get and set the storage version, and provide unit tests to verify versioning logic. This lays the groundwork for safe, enclave-only data migrations by allowing detection and management of storage schema versions. * feat(storage): add storage versioning and migration test Introduce global storage versioning to enable future database migrations. Add an example migration in `init_storage` that checks and updates the storage version. Implement a unit test demonstrating a struct migration from version 0 to 1 using SCALE encoding, ensuring data compatibility across schema changes. * refactor(storage): restrict storage version API visibility Change `get_storage_version` and `set_storage_version` functions from public to private, as they are only used internally. Add a `dead_code` allowance to `set_storage_version` for future migration use.
1 parent 1199980 commit c0a1046

File tree

1 file changed

+105
-0
lines changed
  • tee-worker/omni-executor/executor-storage/src

1 file changed

+105
-0
lines changed

tee-worker/omni-executor/executor-storage/src/lib.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub use intent_id::IntentIdStorage;
2727
use tracing::error;
2828

2929
const STORAGE_DB_PATH: &str = "storage_db";
30+
const STORAGE_VERSION_KEY: &[u8] = b"__storage_version__";
3031

3132
pub type StorageDB = DB;
3233

@@ -82,10 +83,38 @@ pub fn storage_key(storage_name: &str, key: &[u8]) -> Vec<u8> {
8283
.collect()
8384
}
8485

86+
fn get_storage_version(db: &StorageDB) -> u32 {
87+
match db.get(STORAGE_VERSION_KEY) {
88+
Ok(Some(v)) => u32::decode(&mut &v[..]).unwrap_or(0),
89+
Ok(None) => 0,
90+
Err(e) => {
91+
error!("Error getting storage version: {:?}", e);
92+
0
93+
},
94+
}
95+
}
96+
97+
#[allow(dead_code)] // TODO: remove this when adding the first migration
98+
fn set_storage_version(db: &StorageDB, version: u32) -> Result<(), ()> {
99+
let mut opts = WriteOptions::default();
100+
opts.set_sync(true);
101+
db.put_opt(STORAGE_VERSION_KEY, version.encode(), &opts).map_err(|e| {
102+
error!("Error setting storage version: {:?}", e);
103+
})
104+
}
105+
85106
pub async fn init_storage(ws_rpc_endpoint: &str) -> Result<Arc<StorageDB>, ()> {
86107
let db = Arc::new(StorageDB::open_default(STORAGE_DB_PATH).map_err(|e| {
87108
error!("Could not open db: {:?}", e);
88109
})?);
110+
111+
// Migration example: if version == 0, do migration, then set to 1
112+
let current_version = get_storage_version(&db);
113+
if current_version == 0 {
114+
// ... perform migration logic here ...
115+
// set_storage_version(&db, 1)?;
116+
}
117+
89118
let client_factory: SubxtClientFactory<CustomConfig> = SubxtClientFactory::new(ws_rpc_endpoint);
90119
let mut client = client_factory.new_client().await.map_err(|e| {
91120
error!("Could not create client: {:?}", e);
@@ -207,3 +236,79 @@ fn extract_account_id_from_storage_key<K: Decode>(raw_storage_key: &[u8]) -> Res
207236
error!("Error decoding account id: {:?}", e);
208237
})
209238
}
239+
240+
#[cfg(test)]
241+
mod tests {
242+
use super::*;
243+
use std::fs;
244+
use std::path::Path;
245+
246+
#[test]
247+
fn test_storage_version_helpers() {
248+
let db_path = "test_storage_db";
249+
if Path::new(db_path).exists() {
250+
fs::remove_dir_all(db_path).unwrap();
251+
}
252+
let db = StorageDB::open_default(db_path).unwrap();
253+
254+
// Initially, version should be 0
255+
assert_eq!(get_storage_version(&db), 0);
256+
257+
// Set version to 1
258+
set_storage_version(&db, 1).unwrap();
259+
assert_eq!(get_storage_version(&db), 1);
260+
261+
// Set version to 42
262+
set_storage_version(&db, 42).unwrap();
263+
assert_eq!(get_storage_version(&db), 42);
264+
265+
fs::remove_dir_all(db_path).unwrap();
266+
}
267+
268+
#[test]
269+
fn test_struct_migration_v0_to_v1() {
270+
use parity_scale_codec::{Decode, Encode};
271+
use std::fs;
272+
use std::path::Path;
273+
274+
#[derive(Debug, PartialEq, Encode, Decode)]
275+
struct TestDataV0 {
276+
a: u32,
277+
b: String,
278+
}
279+
280+
#[derive(Debug, PartialEq, Encode, Decode)]
281+
struct TestDataV1 {
282+
a: u32,
283+
c: Option<u64>,
284+
}
285+
286+
let db_path = "test_struct_migration_db_scale";
287+
if Path::new(db_path).exists() {
288+
fs::remove_dir_all(db_path).unwrap();
289+
}
290+
let db = StorageDB::open_default(db_path).unwrap();
291+
292+
// Simulate old data (version 0)
293+
let old = TestDataV0 { a: 42, b: "hello".to_string() };
294+
db.put(b"testkey", old.encode()).unwrap();
295+
set_storage_version(&db, 0).unwrap();
296+
297+
// Migration: if version == 0, read old, convert, write new, set version = 1
298+
if get_storage_version(&db) == 0 {
299+
let bytes = db.get(b"testkey").unwrap().unwrap();
300+
let old = TestDataV0::decode(&mut &bytes[..]).unwrap();
301+
let new = TestDataV1 { a: old.a, c: None };
302+
db.put(b"testkey", new.encode()).unwrap();
303+
set_storage_version(&db, 1).unwrap();
304+
}
305+
306+
// Check migrated data
307+
let bytes = db.get(b"testkey").unwrap().unwrap();
308+
let new = TestDataV1::decode(&mut &bytes[..]).unwrap();
309+
assert_eq!(new, TestDataV1 { a: 42, c: None });
310+
assert_eq!(get_storage_version(&db), 1);
311+
312+
fs::remove_dir_all(db_path).unwrap();
313+
}
314+
}

0 commit comments

Comments
 (0)