1use linera_base::{
5 crypto::CryptoHash,
6 identifiers::{BlobId, ChainId, EventId},
7};
8use linera_views::{
9 batch::Batch,
10 store::{KeyValueDatabase, KeyValueStore, ReadableKeyValueStore, WritableKeyValueStore},
11 ViewError,
12};
13use serde::{Deserialize, Serialize};
14use tokio::time::Duration;
15
16use crate::{
17 db_storage::{
18 to_event_key, DbStorage, MultiPartitionBatch, RootKey, BLOB_KEY, BLOB_STATE_KEY, BLOCK_KEY,
19 LITE_CERTIFICATE_KEY, NETWORK_DESCRIPTION_KEY,
20 },
21 Clock,
22};
23
24#[derive(Debug)]
25enum SchemaVersion {
26 Uninitialized,
28 Version0,
31 Version1,
33}
34
35const MIGRATION_WAIT_BEFORE_RETRY_MIN: u64 = 3;
38
39const UNUSED_EMPTY_KEY: &[u8] = &[];
40const MOVABLE_KEYS_0_1: &[u8] = &[1, 2, 3, 4, 5, 7];
47
48const BLOCK_KEY_SIZE: usize = 90;
51
52#[derive(Debug, Serialize, Deserialize)]
53enum BaseKey {
54 ChainState(ChainId),
55 Certificate(CryptoHash),
56 ConfirmedBlock(CryptoHash),
57 Blob(BlobId),
58 BlobState(BlobId),
59 Event(EventId),
60 BlockExporterState(u32),
61 NetworkDescription,
62}
63
64fn map_base_key(base_key: &[u8]) -> Result<(Vec<u8>, Vec<u8>), ViewError> {
68 let base_key = bcs::from_bytes::<BaseKey>(base_key)?;
69 match base_key {
70 BaseKey::ChainState(chain_id) => {
71 let root_key = RootKey::ChainState(chain_id).bytes();
72 Ok((root_key, UNUSED_EMPTY_KEY.to_vec()))
73 }
74 BaseKey::Certificate(hash) => {
75 let root_key = RootKey::ConfirmedBlock(hash).bytes();
76 Ok((root_key, LITE_CERTIFICATE_KEY.to_vec()))
77 }
78 BaseKey::ConfirmedBlock(hash) => {
79 let root_key = RootKey::ConfirmedBlock(hash).bytes();
80 Ok((root_key, BLOCK_KEY.to_vec()))
81 }
82 BaseKey::Blob(blob_id) => {
83 let root_key = RootKey::Blob(blob_id).bytes();
84 Ok((root_key, BLOB_KEY.to_vec()))
85 }
86 BaseKey::BlobState(blob_id) => {
87 let root_key = RootKey::Blob(blob_id).bytes();
88 Ok((root_key, BLOB_STATE_KEY.to_vec()))
89 }
90 BaseKey::Event(event_id) => {
91 let root_key = RootKey::Event(event_id.chain_id).bytes();
92 let key = to_event_key(&event_id);
93 Ok((root_key, key))
94 }
95 BaseKey::BlockExporterState(index) => {
96 let root_key = RootKey::BlockExporterState(index).bytes();
97 Ok((root_key, UNUSED_EMPTY_KEY.to_vec()))
98 }
99 BaseKey::NetworkDescription => {
100 let root_key = RootKey::NetworkDescription.bytes();
101 Ok((root_key, NETWORK_DESCRIPTION_KEY.to_vec()))
102 }
103 }
104}
105
106impl<Database, C> DbStorage<Database, C>
107where
108 Database: KeyValueDatabase + Clone + Send + Sync + 'static,
109 Database::Store: KeyValueStore + Clone + Send + Sync + 'static,
110 C: Clock + Clone + Send + Sync + 'static,
111 Database::Error: From<bcs::Error> + Send + Sync,
112{
113 async fn migrate_shared_partition(
114 &self,
115 first_byte: &u8,
116 keys: Vec<Vec<u8>>,
117 ) -> Result<(), ViewError> {
118 tracing::info!(
119 "Migrating {} keys of shared DB partition starting with {first_byte}",
120 keys.len()
121 );
122 for (index, chunk_keys) in keys.chunks(BLOCK_KEY_SIZE).enumerate() {
123 tracing::info!("Processing chunk {index} of size {}", chunk_keys.len());
124 let chunk_base_keys = chunk_keys
125 .iter()
126 .map(|key| {
127 let mut base_key = vec![*first_byte];
128 base_key.extend(key);
129 base_key
130 })
131 .collect::<Vec<Vec<u8>>>();
132 let store = self.database.open_shared(&[])?;
133 let values = store.read_multi_values_bytes(&chunk_base_keys).await?;
134 let mut batch = MultiPartitionBatch::new();
135 for (base_key, value) in chunk_base_keys.iter().zip(values) {
136 let value = value.ok_or_else(|| ViewError::MissingEntries("migration".into()))?;
137 let (root_key, key) = map_base_key(base_key)?;
138 batch.put_key_value(root_key, key, value);
139 }
140 self.write_batch(batch).await?;
141 let mut batch = Batch::new();
143 for key in chunk_base_keys {
144 batch.delete_key(key.to_vec());
145 }
146 store.write_batch(batch).await?;
147 }
148 Ok(())
149 }
150
151 async fn migrate_v0_to_v1(&self) -> Result<(), ViewError> {
152 for first_byte in MOVABLE_KEYS_0_1 {
153 let store = self.database.open_shared(&[])?;
154 let keys = store.find_keys_by_prefix(&[*first_byte]).await?;
155 self.migrate_shared_partition(first_byte, keys).await?;
156 }
157 Ok(())
158 }
159
160 pub async fn migrate_if_needed(&self) -> Result<(), ViewError> {
162 loop {
163 if matches!(
164 self.get_storage_state().await?,
165 SchemaVersion::Uninitialized | SchemaVersion::Version1
166 ) {
167 return Ok(());
169 }
170 let result = self.migrate_v0_to_v1().await;
171 if let Err(ViewError::MissingEntries(_)) = result {
172 tracing::warn!(
173 "It looks like a migration is already in progress on this database. \
174 I will wait for {:?} minutes and retry.",
175 MIGRATION_WAIT_BEFORE_RETRY_MIN
176 );
177 tokio::time::sleep(Duration::from_secs(MIGRATION_WAIT_BEFORE_RETRY_MIN * 60)).await;
179 continue;
180 }
181 return result;
182 }
183 }
184
185 async fn get_storage_state(&self) -> Result<SchemaVersion, ViewError> {
186 let store = self.database.open_shared(&[])?;
187 let key = bcs::to_bytes(&BaseKey::NetworkDescription).unwrap();
188 if store.contains_key(&key).await? {
189 return Ok(SchemaVersion::Version0);
190 }
191
192 let root_key = RootKey::NetworkDescription.bytes();
193 let store = self.database.open_shared(&root_key)?;
194 if store.contains_key(NETWORK_DESCRIPTION_KEY).await? {
195 return Ok(SchemaVersion::Version1);
196 }
197
198 Ok(SchemaVersion::Uninitialized)
199 }
200
201 pub async fn assert_is_migrated_storage(&self) -> Result<(), ViewError> {
203 let state = self.get_storage_state().await?;
204 assert!(matches!(
205 state,
206 SchemaVersion::Uninitialized | SchemaVersion::Version1
207 ));
208 Ok(())
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use std::{
215 collections::{BTreeMap, HashMap},
216 marker::PhantomData,
217 ops::Deref,
218 };
219
220 use linera_base::{
221 crypto::CryptoHash,
222 identifiers::{BlobId, BlobType, ChainId, EventId, StreamId, StreamName},
223 };
224 #[cfg(feature = "rocksdb")]
225 use linera_views::rocks_db::RocksDbDatabase;
226 #[cfg(feature = "scylladb")]
227 use linera_views::scylla_db::ScyllaDbDatabase;
228 use linera_views::{
229 batch::Batch,
230 memory::MemoryDatabase,
231 random::make_deterministic_rng,
232 store::{
233 KeyValueDatabase, KeyValueStore, ReadableKeyValueStore, TestKeyValueDatabase,
234 WritableKeyValueStore,
235 },
236 ViewError,
237 };
238 use rand::{distributions, Rng};
239 use test_case::test_case;
240
241 use crate::{
242 db_storage::RestrictedEventId,
243 migration::{
244 BaseKey, RootKey, BLOB_KEY, BLOB_STATE_KEY, BLOCK_KEY, LITE_CERTIFICATE_KEY,
245 NETWORK_DESCRIPTION_KEY,
246 },
247 DbStorage, StorageCacheConfig, WallClock,
248 };
249
250 #[derive(Clone, Debug, Eq, PartialEq)]
251 #[allow(clippy::type_complexity)]
252 struct StorageState {
253 chain_ids_key_values: BTreeMap<ChainId, Vec<(Vec<u8>, Vec<u8>)>>,
254 certificates: BTreeMap<CryptoHash, Vec<u8>>,
255 confirmed_blocks: BTreeMap<CryptoHash, Vec<u8>>,
256 blobs: BTreeMap<BlobId, Vec<u8>>,
257 blob_states: BTreeMap<BlobId, Vec<u8>>,
258 events: HashMap<EventId, Vec<u8>>,
259 block_exporter_states: BTreeMap<u32, Vec<(Vec<u8>, Vec<u8>)>>,
260 network_description: Option<Vec<u8>>,
261 }
262
263 impl StorageState {
264 fn append_storage_state(&mut self, storage_state: StorageState) {
265 self.chain_ids_key_values
266 .extend(storage_state.chain_ids_key_values);
267 self.certificates.extend(storage_state.certificates);
268 self.confirmed_blocks.extend(storage_state.confirmed_blocks);
269 self.blobs.extend(storage_state.blobs);
270 self.blob_states.extend(storage_state.blob_states);
271 self.events.extend(storage_state.events);
272 self.block_exporter_states
273 .extend(storage_state.block_exporter_states);
274 if let Some(value) = storage_state.network_description {
275 assert!(self.network_description.is_none());
276 self.network_description = Some(value);
277 }
278 }
279 }
280
281 fn create_vector(rng: &mut impl Rng, len: usize) -> Vec<u8> {
282 rng.sample_iter(distributions::Standard).take(len).collect()
283 }
284
285 fn get_hash(rng: &mut impl Rng) -> CryptoHash {
286 let rnd_val = rng.gen::<usize>();
287 CryptoHash::test_hash(format!("rnd_val={rnd_val}"))
288 }
289
290 fn get_stream_id(rng: &mut impl Rng) -> StreamId {
291 let stream_name = StreamName(create_vector(rng, 10));
292 StreamId::system(stream_name)
293 }
294
295 fn get_event_id(rng: &mut impl Rng) -> EventId {
296 let hash = get_hash(rng);
297 let chain_id = ChainId(hash);
298 let stream_id = get_stream_id(rng);
299 let index = rng.gen::<u32>();
300 EventId {
301 chain_id,
302 stream_id,
303 index,
304 }
305 }
306
307 fn get_storage_state() -> StorageState {
308 let mut rng = make_deterministic_rng();
309 let key_size = 5;
310 let value_size = 10;
311 let chain_id_count = 10;
313 let n_key = 1;
314 let mut chain_ids_key_values = BTreeMap::new();
315 for _i_chain in 0..chain_id_count {
316 let hash = get_hash(&mut rng);
317 let chain_id = ChainId(hash);
318 let mut key_values = Vec::new();
319 for _i_key in 0..n_key {
320 let key = create_vector(&mut rng, key_size);
321 let value = create_vector(&mut rng, value_size);
322 key_values.push((key, value));
323 }
324 key_values.sort_unstable();
325 chain_ids_key_values.insert(chain_id, key_values);
326 }
327 let certificates_count = 10;
329 let mut certificates = BTreeMap::new();
330 for _i_certificate in 0..certificates_count {
331 let hash = get_hash(&mut rng);
332 let value = create_vector(&mut rng, value_size);
333 certificates.insert(hash, value);
334 }
335 let blocks_count = 10;
337 let mut confirmed_blocks = BTreeMap::new();
338 for _i_block in 0..blocks_count {
339 let hash = get_hash(&mut rng);
340 let value = create_vector(&mut rng, value_size);
341 certificates.insert(hash, value);
342 let value = create_vector(&mut rng, value_size);
343 confirmed_blocks.insert(hash, value);
344 }
345 let blobs_count = 2;
347 let mut blobs = BTreeMap::new();
348 for _i_blob in 0..blobs_count {
349 let hash = get_hash(&mut rng);
350 let blob_id = BlobId {
351 blob_type: BlobType::Data,
352 hash,
353 };
354 let value = create_vector(&mut rng, value_size);
355 blobs.insert(blob_id, value);
356 }
357 let blob_states_count = 2;
359 let mut blob_states = BTreeMap::new();
360 for _i_blob_state in 0..blob_states_count {
361 let hash = get_hash(&mut rng);
362 let blob_id = BlobId {
363 blob_type: BlobType::Data,
364 hash,
365 };
366 let value = create_vector(&mut rng, value_size);
367 blob_states.insert(blob_id, value);
368 }
369 let events_count = 2;
371 let mut events = HashMap::new();
372 for _i_event in 0..events_count {
373 let event_id = get_event_id(&mut rng);
374 let value = create_vector(&mut rng, value_size);
375 events.insert(event_id, value);
376 }
377 let block_exporters_count = 2;
379 let n_key = 1;
380 let mut block_exporter_states = BTreeMap::new();
381 for _i_block_export in 0..block_exporters_count {
382 let index = rng.gen::<u32>();
383 let mut key_values = Vec::new();
384 for _i_key in 0..n_key {
385 let key = create_vector(&mut rng, key_size);
386 let value = create_vector(&mut rng, value_size);
387 key_values.push((key, value));
388 }
389 key_values.sort_unstable();
390 block_exporter_states.insert(index, key_values);
391 }
392 let network_description = Some(create_vector(&mut rng, value_size));
394 StorageState {
395 chain_ids_key_values,
396 certificates,
397 confirmed_blocks,
398 blobs,
399 blob_states,
400 events,
401 block_exporter_states,
402 network_description,
403 }
404 }
405
406 async fn write_storage_state_old_schema<D>(
407 database: &D,
408 storage_state: StorageState,
409 ) -> Result<(), ViewError>
410 where
411 D: KeyValueDatabase + Clone + Send + Sync + 'static,
412 D::Store: KeyValueStore + Clone + Send + Sync + 'static,
413 D::Error: Send + Sync,
414 {
415 for (chain_id, key_values) in storage_state.chain_ids_key_values {
416 let root_key = bcs::to_bytes(&BaseKey::ChainState(chain_id))?;
417 let store = database.open_shared(&root_key)?;
418 let mut batch = Batch::new();
419 for (key, value) in key_values {
420 batch.put_key_value_bytes(key, value);
421 }
422 store.write_batch(batch).await?;
423 }
424 for (index, key_values) in storage_state.block_exporter_states {
425 let root_key = bcs::to_bytes(&BaseKey::BlockExporterState(index))?;
426 let store = database.open_shared(&root_key)?;
427 let mut batch = Batch::new();
428 for (key, value) in key_values {
429 batch.put_key_value_bytes(key, value);
430 }
431 store.write_batch(batch).await?;
432 }
433 let mut batch = Batch::new();
435 for (hash, value) in storage_state.certificates {
436 let key = bcs::to_bytes(&BaseKey::Certificate(hash))?;
437 batch.put_key_value_bytes(key, value);
438 }
439 for (hash, value) in storage_state.confirmed_blocks {
440 let key = bcs::to_bytes(&BaseKey::ConfirmedBlock(hash))?;
441 batch.put_key_value_bytes(key, value);
442 }
443 for (blob_id, value) in storage_state.blobs {
444 let key = bcs::to_bytes(&BaseKey::Blob(blob_id))?;
445 batch.put_key_value_bytes(key, value);
446 }
447 for (blob_id, value) in storage_state.blob_states {
448 let key = bcs::to_bytes(&BaseKey::BlobState(blob_id))?;
449 batch.put_key_value_bytes(key, value);
450 }
451 for (event_id, value) in storage_state.events {
452 let key = bcs::to_bytes(&BaseKey::Event(event_id))?;
453 batch.put_key_value_bytes(key, value);
454 }
455 if let Some(network_description) = storage_state.network_description {
456 let key = bcs::to_bytes(&BaseKey::NetworkDescription)?;
457 batch.put_key_value_bytes(key, network_description);
458 }
459 let store = database.open_shared(&[])?;
460 store.write_batch(batch).await?;
461 Ok(())
462 }
463
464 fn is_valid_root_key(root_key: &[u8]) -> bool {
465 if root_key.is_empty() {
466 return false;
468 }
469 if root_key == [4] {
470 return false;
472 }
473 true
474 }
475
476 async fn read_storage_state_new_schema<D>(database: &D) -> Result<StorageState, ViewError>
477 where
478 D: KeyValueDatabase + Clone + Send + Sync + 'static,
479 D::Store: KeyValueStore + Clone + Send + Sync + 'static,
480 D::Error: Send + Sync,
481 {
482 let mut chain_ids_key_values = BTreeMap::new();
483 let mut certificates = BTreeMap::new();
484 let mut confirmed_blocks = BTreeMap::new();
485 let mut blobs = BTreeMap::new();
486 let mut blob_states = BTreeMap::new();
487 let mut events = HashMap::new();
488 let mut block_exporter_states = BTreeMap::new();
489 let mut network_description = None;
490 let bcs_root_keys = database.list_root_keys().await?;
491 for bcs_root_key in bcs_root_keys {
492 if is_valid_root_key(&bcs_root_key) {
493 let root_key = bcs::from_bytes(&bcs_root_key)?;
494 match root_key {
495 RootKey::ChainState(chain_id) => {
496 let store = database.open_shared(&bcs_root_key)?;
497 let key_values = store.find_key_values_by_prefix(&[]).await?;
498 chain_ids_key_values.insert(chain_id, key_values);
499 }
500 RootKey::ConfirmedBlock(hash) => {
501 let store = database.open_shared(&bcs_root_key)?;
502 let value = store.read_value_bytes(LITE_CERTIFICATE_KEY).await?;
503 if let Some(value) = value {
504 certificates.insert(hash, value);
505 }
506 let value = store.read_value_bytes(BLOCK_KEY).await?;
507 if let Some(value) = value {
508 confirmed_blocks.insert(hash, value);
509 }
510 }
511 RootKey::Blob(blob_id) => {
512 let store = database.open_shared(&bcs_root_key)?;
513 let value = store.read_value_bytes(BLOB_KEY).await?;
514 if let Some(value) = value {
515 blobs.insert(blob_id, value);
516 }
517 let value = store.read_value_bytes(BLOB_STATE_KEY).await?;
518 if let Some(value) = value {
519 blob_states.insert(blob_id, value);
520 }
521 }
522 RootKey::Event(chain_id) => {
523 let store = database.open_shared(&bcs_root_key)?;
524 let key_values = store.find_key_values_by_prefix(&[]).await?;
525 for (key, value) in key_values {
526 let restricted_event_id = bcs::from_bytes::<RestrictedEventId>(&key)?;
527 let event_id = EventId {
528 chain_id,
529 stream_id: restricted_event_id.stream_id,
530 index: restricted_event_id.index,
531 };
532 events.insert(event_id, value);
533 }
534 }
535 RootKey::Placeholder => {
536 }
538 RootKey::NetworkDescription => {
539 let store = database.open_shared(&bcs_root_key)?;
540 let value = store.read_value_bytes(NETWORK_DESCRIPTION_KEY).await?;
541 if let Some(value) = value {
542 network_description = Some(value);
543 }
544 }
545 RootKey::BlockExporterState(index) => {
546 let store = database.open_shared(&bcs_root_key)?;
547 let key_values = store.find_key_values_by_prefix(&[]).await?;
548 block_exporter_states.insert(index, key_values);
549 }
550 RootKey::BlockByHeight(_) => {
551 }
553 }
554 }
555 }
556 Ok(StorageState {
557 chain_ids_key_values,
558 certificates,
559 confirmed_blocks,
560 blobs,
561 blob_states,
562 events,
563 block_exporter_states,
564 network_description,
565 })
566 }
567
568 async fn test_storage_migration<D>() -> Result<(), ViewError>
569 where
570 D: TestKeyValueDatabase + Clone + Send + Sync + 'static,
571 D::Store: KeyValueStore + Clone + Send + Sync + 'static,
572 D::Error: Send + Sync,
573 {
574 let database = D::connect_test_namespace().await?;
575 let mut storage_state = get_storage_state();
577 write_storage_state_old_schema(&database, storage_state.clone()).await?;
578 let cache_sizes = StorageCacheConfig {
580 blob_cache_size: 1000,
581 confirmed_block_cache_size: 1000,
582 certificate_cache_size: 1000,
583 certificate_raw_cache_size: 1000,
584 event_cache_size: 1000,
585 block_hash_by_height_cache_size: 1000,
586 cache_cleanup_interval_secs: crate::DEFAULT_CLEANUP_INTERVAL_SECS,
587 };
588 let storage = DbStorage::<D, WallClock>::new(database, None, cache_sizes, WallClock);
589 storage.migrate_if_needed().await?;
590 let read_storage_state = read_storage_state_new_schema(storage.database.deref()).await?;
592 assert_eq!(read_storage_state, storage_state);
593 let mut appended_state = get_storage_state();
596 appended_state.network_description = None;
597 write_storage_state_old_schema(storage.database.deref(), appended_state.clone()).await?;
598 storage.migrate_if_needed().await?;
599 storage_state.append_storage_state(appended_state);
600 let read_storage_state = read_storage_state_new_schema(storage.database.deref()).await?;
601 assert_eq!(read_storage_state, storage_state);
602 Ok(())
603 }
604
605 #[test_case(PhantomData::<MemoryDatabase>; "MemoryDatabase")]
606 #[cfg_attr(with_rocksdb, test_case(PhantomData::<RocksDbDatabase>; "RocksDbDatabase"))]
607 #[cfg_attr(with_scylladb, test_case(PhantomData::<ScyllaDbDatabase>; "ScyllaDbDatabase"))]
608 #[tokio::test]
609 async fn test_storage_migration_cases<D>(_storage_type: PhantomData<D>) -> Result<(), ViewError>
610 where
611 D: TestKeyValueDatabase + Clone + Send + Sync + 'static,
612 D::Store: KeyValueStore + Clone + Send + Sync + 'static,
613 D::Error: Send + Sync,
614 {
615 test_storage_migration::<D>().await
616 }
617}