From 9ed81ba1a3d16c081391e7e4700c83180eae8b19 Mon Sep 17 00:00:00 2001 From: aldenhu Date: Tue, 10 May 2022 16:21:11 -0700 Subject: [PATCH] [storage] TreeState includes state checkpoint and write sets after it Startup embeds TreeState hence changed accordingly -- for one thing, StateStore can no longer serve StartupInfo alone -- AptosDB needs to do it by leveraging multiple stores. in preparation for per-block SMT update Closes: #925 --- .../components/in_memory_state_calculator.rs | 35 ++++++-- .../state-sync-driver/src/tests/utils.rs | 2 +- storage/aptosdb/src/aptosdb_test.rs | 49 +++++------ storage/aptosdb/src/backup/backup_handler.rs | 37 ++++---- storage/aptosdb/src/backup/restore_handler.rs | 16 ++-- .../src/ledger_store/ledger_info_test.rs | 31 +++---- storage/aptosdb/src/ledger_store/mod.rs | 43 ++++----- storage/aptosdb/src/lib.rs | 87 +++++++++++++++---- storage/aptosdb/src/test_helper.rs | 12 +++ .../src/backup_types/state_snapshot/tests.rs | 7 +- .../src/backup_types/transaction/restore.rs | 2 +- storage/storage-interface/src/lib.rs | 38 ++++++-- .../src/verified_state_view.rs | 16 +++- .../src/storage_service_test.rs | 2 +- testsuite/smoke-test/src/storage.rs | 5 ++ 15 files changed, 242 insertions(+), 140 deletions(-) diff --git a/execution/executor/src/components/in_memory_state_calculator.rs b/execution/executor/src/components/in_memory_state_calculator.rs index 2c35b40a4907f..ca560531b86da 100644 --- a/execution/executor/src/components/in_memory_state_calculator.rs +++ b/execution/executor/src/components/in_memory_state_calculator.rs @@ -4,7 +4,7 @@ use crate::components::apply_chunk_output::ParsedTransactionOutput; use anyhow::{anyhow, bail, Result}; use aptos_crypto::{hash::CryptoHash, HashValue}; -use aptos_state_view::account_with_state_cache::AsAccountWithStateCache; +use aptos_state_view::{account_with_state_cache::AsAccountWithStateCache, StateViewId}; use aptos_types::{ account_view::AccountView, epoch_state::EpochState, @@ -25,7 +25,9 @@ use std::{ sync::Arc, }; use storage_interface::{ - in_memory_state::InMemoryState, verified_state_view::StateCache, DbReader, TreeState, + in_memory_state::InMemoryState, + verified_state_view::{StateCache, VerifiedStateView}, + DbReader, TreeState, }; pub trait IntoLedgerView { @@ -33,17 +35,33 @@ pub trait IntoLedgerView { } impl IntoLedgerView for TreeState { - fn into_ledger_view(self, _db: &Arc) -> Result { - let checkpoint_num_txns = self.num_transactions; - let checkpoint = - InMemoryState::new_at_checkpoint(self.state_root_hash, checkpoint_num_txns); - + fn into_ledger_view(self, db: &Arc) -> Result { + let checkpoint_num_txns = self + .num_transactions + .checked_sub(self.write_sets_after_checkpoint.len() as LeafCount) + .ok_or_else(|| anyhow!("State checkpoint pre-dates genesis."))?; + let checkpoint_state = + InMemoryState::new_at_checkpoint(self.state_checkpoint_hash, checkpoint_num_txns); + + let checkpoint_state_view = VerifiedStateView::new( + StateViewId::Miscellaneous, + db.clone(), + checkpoint_num_txns.checked_sub(1), + self.state_checkpoint_hash, + checkpoint_state.checkpoint.clone(), + ); + let write_sets: Vec<_> = self.write_sets_after_checkpoint.iter().collect(); + checkpoint_state_view.prime_cache_by_write_set(&write_sets)?; + let state_cache = checkpoint_state_view.into_state_cache(); + let calculator = + InMemoryStateCalculator::new(&checkpoint_state, state_cache, checkpoint_num_txns); + let state = calculator.calculate_for_write_sets_after_checkpoint(&write_sets)?; let transaction_accumulator = Arc::new(InMemoryAccumulator::new( self.ledger_frozen_subtree_hashes, self.num_transactions, )?); - Ok(ExecutedTrees::new(checkpoint, transaction_accumulator)) + Ok(ExecutedTrees::new(state, transaction_accumulator)) } } @@ -280,7 +298,6 @@ impl InMemoryStateCalculator { Ok((result_state, self.state_cache)) } - #[allow(dead_code)] pub fn calculate_for_write_sets_after_checkpoint( mut self, write_sets: &[&WriteSet], diff --git a/state-sync/state-sync-v2/state-sync-driver/src/tests/utils.rs b/state-sync/state-sync-v2/state-sync-driver/src/tests/utils.rs index 1940afed83c83..65355066af17c 100644 --- a/state-sync/state-sync-v2/state-sync-driver/src/tests/utils.rs +++ b/state-sync/state-sync-v2/state-sync-driver/src/tests/utils.rs @@ -125,7 +125,7 @@ pub fn create_startup_info() -> StartupInfo { StartupInfo::new( create_epoch_ending_ledger_info(), Some(EpochState::empty()), - TreeState::new(0, vec![], HashValue::random()), + TreeState::new_at_state_checkpoint(0, vec![], HashValue::random()), None, ) } diff --git a/storage/aptosdb/src/aptosdb_test.rs b/storage/aptosdb/src/aptosdb_test.rs index 51a75328245ab..1b7df5df66b98 100644 --- a/storage/aptosdb/src/aptosdb_test.rs +++ b/storage/aptosdb/src/aptosdb_test.rs @@ -2,21 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - get_first_seq_num_and_limit, schema::jellyfish_merkle_node::JellyfishMerkleNodeSchema, - test_helper, test_helper::arb_blocks_to_commit, AptosDB, ROCKSDB_PROPERTIES, + get_first_seq_num_and_limit, test_helper, + test_helper::{arb_blocks_to_commit, put_as_state_root, put_transaction_info}, + AptosDB, ROCKSDB_PROPERTIES, }; -use aptos_crypto::{ - hash::{CryptoHash, SPARSE_MERKLE_PLACEHOLDER_HASH}, - HashValue, -}; -use aptos_jellyfish_merkle::node_type::{Node, NodeKey}; +use aptos_crypto::{hash::CryptoHash, HashValue}; use aptos_temppath::TempPath; use aptos_types::{ proof::SparseMerkleLeafNode, - state_store::{ - state_key::StateKey, - state_value::{StateKeyAndValue, StateValue}, - }, + state_store::{state_key::StateKey, state_value::StateValue}, transaction::{ExecutionStatus, TransactionInfo, PRE_GENESIS_VERSION}, }; use proptest::prelude::*; @@ -87,42 +81,41 @@ fn test_get_latest_tree_state() { // entirely emtpy db let empty = db.get_latest_tree_state().unwrap(); - assert_eq!( - empty, - TreeState::new(0, vec![], *SPARSE_MERKLE_PLACEHOLDER_HASH,) - ); + assert_eq!(empty, TreeState::new_empty(),); // unbootstrapped db with pre-genesis state let key = StateKey::Raw(String::from("test_key").into_bytes()); let value = StateValue::from(String::from("test_val").into_bytes()); - db.db - .put::( - &NodeKey::new_empty_path(PRE_GENESIS_VERSION), - &Node::new_leaf( - key.hash(), - StateKeyAndValue::new(key.clone(), value.clone()), - ), - ) - .unwrap(); + put_as_state_root(&db, PRE_GENESIS_VERSION, key.clone(), value.clone()); let hash = SparseMerkleLeafNode::new(key.hash(), value.hash()).hash(); let pre_genesis = db.get_latest_tree_state().unwrap(); - assert_eq!(pre_genesis, TreeState::new(0, vec![], hash)); + assert_eq!( + pre_genesis, + TreeState::new_at_state_checkpoint(0, vec![], hash) + ); // bootstrapped db (any transaction info is in) + put_as_state_root(&db, 0, key, value); let txn_info = TransactionInfo::new( HashValue::random(), HashValue::random(), HashValue::random(), - Some(HashValue::random()), + Some(hash), 0, ExecutionStatus::MiscellaneousError(None), ); - test_helper::put_transaction_info(&db, 0, &txn_info); + put_transaction_info(&db, 0, &txn_info); + let bootstrapped = db.get_latest_tree_state().unwrap(); assert_eq!( bootstrapped, - TreeState::new(1, vec![txn_info.hash()], txn_info.state_change_hash(),) + TreeState::new( + 1, + vec![txn_info.hash()], + txn_info.state_checkpoint_hash().unwrap(), + Vec::new() + ), ); } diff --git a/storage/aptosdb/src/backup/backup_handler.rs b/storage/aptosdb/src/backup/backup_handler.rs index 590f37bc1aafd..2c89e753775f8 100644 --- a/storage/aptosdb/src/backup/backup_handler.rs +++ b/storage/aptosdb/src/backup/backup_handler.rs @@ -11,7 +11,7 @@ use crate::{ state_store::StateStore, transaction_store::TransactionStore, }; -use anyhow::{anyhow, ensure, Result}; +use anyhow::{ensure, Result}; use aptos_crypto::hash::HashValue; use aptos_jellyfish_merkle::iterator::JellyfishMerkleIterator; use aptos_types::{ @@ -132,23 +132,24 @@ impl BackupHandler { pub fn get_db_state(&self) -> Result> { self.ledger_store .get_startup_info()? - .map(|s| { - Ok(DbState { - epoch: s.get_epoch_state().epoch, - committed_version: s - .committed_tree_state - .num_transactions - .checked_sub(1) - .ok_or_else(|| anyhow!("Bootstrapped DB has no transactions."))?, - synced_version: s - .synced_tree_state - .as_ref() - .unwrap_or(&s.committed_tree_state) - .num_transactions - .checked_sub(1) - .ok_or_else(|| anyhow!("Bootstrapped DB has no transactions."))?, - }) - }) + .map( + |(latest_li, epoch_state_if_not_in_li, synced_version_opt)| { + Ok(DbState { + epoch: latest_li + .ledger_info() + .next_epoch_state() + .unwrap_or_else(|| { + epoch_state_if_not_in_li + .as_ref() + .expect("EpochState must exist") + }) + .epoch, + committed_version: latest_li.ledger_info().version(), + synced_version: synced_version_opt + .unwrap_or_else(|| latest_li.ledger_info().version()), + }) + }, + ) .transpose() } diff --git a/storage/aptosdb/src/backup/restore_handler.rs b/storage/aptosdb/src/backup/restore_handler.rs index 9ce8de5ecef21..de9e926c293f2 100644 --- a/storage/aptosdb/src/backup/restore_handler.rs +++ b/storage/aptosdb/src/backup/restore_handler.rs @@ -92,19 +92,19 @@ impl RestoreHandler { ) } - pub fn get_tree_state(&self, num_transactions: LeafCount) -> Result { + pub fn get_tree_state(&self, version: Option) -> Result { + let num_transactions: LeafCount = version.map_or(0, |v| v + 1); let frozen_subtrees = self .ledger_store .get_frozen_subtree_hashes(num_transactions)?; - let state_root_hash = if num_transactions == 0 { - self.state_store + let state_root_hash = match version { + None => self + .state_store .get_root_hash_option(PRE_GENESIS_VERSION)? - .unwrap_or(*SPARSE_MERKLE_PLACEHOLDER_HASH) - } else { - self.state_store.get_root_hash(num_transactions - 1)? + .unwrap_or(*SPARSE_MERKLE_PLACEHOLDER_HASH), + Some(ver) => self.state_store.get_root_hash(ver)?, }; - - Ok(TreeState::new( + Ok(TreeState::new_at_state_checkpoint( num_transactions, frozen_subtrees, state_root_hash, diff --git a/storage/aptosdb/src/ledger_store/ledger_info_test.rs b/storage/aptosdb/src/ledger_store/ledger_info_test.rs index 5271ce7651f49..dc3ec7ca2875f 100644 --- a/storage/aptosdb/src/ledger_store/ledger_info_test.rs +++ b/storage/aptosdb/src/ledger_store/ledger_info_test.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::*; -use crate::{change_set::ChangeSet, AptosDB}; +use crate::AptosDB; use aptos_temppath::TempPath; use ledger_info_test_utils::*; use proptest::{collection::vec, prelude::*}; @@ -109,29 +109,22 @@ proptest! { let db = set_up(&tmp_dir, &ledger_infos_with_sigs); put_transaction_infos(&db, &txn_infos); - let startup_info = db.ledger_store.get_startup_info().unwrap().unwrap(); - let latest_li = ledger_infos_with_sigs.last().unwrap().ledger_info(); - assert_eq!(startup_info.latest_ledger_info, *ledger_infos_with_sigs.last().unwrap()); - let expected_epoch_state = if latest_li.next_epoch_state().is_none() { - Some(db.ledger_store.get_epoch_state(latest_li.epoch()).unwrap()) + let (latest_li, epoch_state, synced_version_opt) = db.ledger_store.get_startup_info().unwrap().unwrap(); + assert_eq!(latest_li, *ledger_infos_with_sigs.last().unwrap()); + let li = latest_li.ledger_info(); + let expected_epoch_state = if li.next_epoch_state().is_none() { + Some(db.ledger_store.get_epoch_state(li.epoch()).unwrap()) } else { None }; - assert_eq!(startup_info.latest_epoch_state, expected_epoch_state); - let committed_version = get_last_version(&ledger_infos_with_sigs); - assert_eq!( - startup_info.committed_tree_state.state_root_hash, - txn_infos[committed_version as usize].state_change_hash(), - ); + assert_eq!(epoch_state, expected_epoch_state); let synced_version = (txn_infos.len() - 1) as u64; - if synced_version > committed_version { - assert_eq!( - startup_info.synced_tree_state.unwrap().state_root_hash, - txn_infos.last().unwrap().state_change_hash(), - ); + let expected_synced_version = if synced_version > li.version() { + Some(synced_version) } else { - assert!(startup_info.synced_tree_state.is_none()); - } + None + }; + assert_eq!(synced_version_opt, expected_synced_version); } } diff --git a/storage/aptosdb/src/ledger_store/mod.rs b/storage/aptosdb/src/ledger_store/mod.rs index e3a38970e4386..01c596c0af857 100644 --- a/storage/aptosdb/src/ledger_store/mod.rs +++ b/storage/aptosdb/src/ledger_store/mod.rs @@ -32,7 +32,6 @@ use arc_swap::ArcSwap; use itertools::Itertools; use schemadb::{ReadOptions, SchemaBatch, SchemaIterator, DB}; use std::{ops::Deref, sync::Arc}; -use storage_interface::{StartupInfo, TreeState}; #[derive(Debug)] pub struct LedgerStore { @@ -157,23 +156,19 @@ impl LedgerStore { Ok(latest_epoch_state.clone()) } - pub fn get_tree_state( - &self, - num_transactions: LeafCount, - transaction_info: TransactionInfo, - ) -> Result { - Ok(TreeState::new( - num_transactions, - self.get_frozen_subtree_hashes(num_transactions)?, - transaction_info.state_change_hash(), - )) - } - pub fn get_frozen_subtree_hashes(&self, num_transactions: LeafCount) -> Result> { Accumulator::get_frozen_subtree_hashes(self, num_transactions) } - pub fn get_startup_info(&self) -> Result> { + pub fn get_startup_info( + &self, + ) -> Result< + Option<( + LedgerInfoWithSignatures, // latest ledger info + Option, // latest epoch state if not in the above ledger info + Option, // synced version if newer than the ledger info + )>, + > { // Get the latest ledger info. Return None if not bootstrapped. let latest_ledger_info = match self.get_latest_ledger_info_option() { Some(x) => x, @@ -188,26 +183,18 @@ impl LedgerStore { }; let li_version = latest_ledger_info.ledger_info().version(); - let (latest_version, latest_txn_info) = self.get_latest_transaction_info()?; + let (latest_version, _) = self.get_latest_transaction_info()?; assert!(latest_version >= li_version); - let (commited_tree_state, synced_tree_state) = if latest_version == li_version { - ( - self.get_tree_state(latest_version + 1, latest_txn_info)?, - None, - ) + let synced_version_opt = if latest_version == li_version { + None } else { - let commited_txn_info = self.get_transaction_info(li_version)?; - ( - self.get_tree_state(li_version + 1, commited_txn_info)?, - Some(self.get_tree_state(latest_version + 1, latest_txn_info)?), - ) + Some(latest_version) }; - Ok(Some(StartupInfo::new( + Ok(Some(( latest_ledger_info, latest_epoch_state_if_not_in_li, - commited_tree_state, - synced_tree_state, + synced_version_opt, ))) } diff --git a/storage/aptosdb/src/lib.rs b/storage/aptosdb/src/lib.rs index 8f52383f24e7d..63198d92d9218 100644 --- a/storage/aptosdb/src/lib.rs +++ b/storage/aptosdb/src/lib.rs @@ -443,6 +443,47 @@ impl AptosDB { }) } + fn get_tree_state(&self, version: Option) -> Result { + let num_transactions = version.map_or(0, |v| v + 1); + + let frozen_subtrees = self + .ledger_store + .get_frozen_subtree_hashes(num_transactions)?; + + let checkpoint_version = self + .state_store + .find_latest_persisted_version_less_than(num_transactions)?; + let checkpoint_root_hash = checkpoint_version + .map_or(Ok(*SPARSE_MERKLE_PLACEHOLDER_HASH), |ver| { + self.state_store.get_root_hash(ver) + })?; + + let write_sets_after_checkpoint = self.transaction_store.get_write_sets( + Self::state_checkpoint_next_version(checkpoint_version), + num_transactions, + )?; + + Ok(TreeState::new( + num_transactions, + frozen_subtrees, + checkpoint_root_hash, + write_sets_after_checkpoint, + )) + } + + fn state_checkpoint_next_version(checkpoint_version: Option) -> Version { + match checkpoint_version { + None => 0, + Some(v) => { + if v == PRE_GENESIS_VERSION { + 0 + } else { + v + 1 + } + } + } + } + // ================================== Backup APIs =================================== /// Gets an instance of `BackupHandler` for data backup purpose. @@ -1004,7 +1045,27 @@ impl DbReader for AptosDB { } fn get_startup_info(&self) -> Result> { - gauged_api("get_startup_info", || self.ledger_store.get_startup_info()) + gauged_api("get_startup_info", || { + self.ledger_store + .get_startup_info()? + .map( + |(latest_ledger_info, latest_epoch_state_if_not_in_li, synced_version_opt)| { + let committed_tree_state = + self.get_tree_state(Some(latest_ledger_info.ledger_info().version()))?; + let synced_tree_state = synced_version_opt + .map(|v| self.get_tree_state(Some(v))) + .transpose()?; + + Ok(StartupInfo::new( + latest_ledger_info, + latest_epoch_state_if_not_in_li, + committed_tree_state, + synced_tree_state, + )) + }, + ) + .transpose() + }) } fn get_state_value_with_proof_by_version( @@ -1020,25 +1081,13 @@ impl DbReader for AptosDB { fn get_latest_tree_state(&self) -> Result { gauged_api("get_latest_tree_state", || { - let tree_state = match self.ledger_store.get_latest_transaction_info_option()? { - Some((version, txn_info)) => { - self.ledger_store.get_tree_state(version + 1, txn_info)? - } - None => TreeState::new( - 0, - vec![], - self.state_store - .get_root_hash_option(PRE_GENESIS_VERSION)? - .unwrap_or(*SPARSE_MERKLE_PLACEHOLDER_HASH), - ), - }; + let latest_version = self + .ledger_store + .get_latest_transaction_info_option()? + .map(|(version, _)| version); + let tree_state = self.get_tree_state(latest_version)?; - info!( - num_transactions = tree_state.num_transactions, - state_root_hash = %tree_state.state_root_hash, - description = tree_state.describe(), - "Got latest TreeState." - ); + info!(tree_state = tree_state, "Got latest TreeState."); Ok(tree_state) }) diff --git a/storage/aptosdb/src/test_helper.rs b/storage/aptosdb/src/test_helper.rs index 991adcef8eb5a..d41b83a8c8d51 100644 --- a/storage/aptosdb/src/test_helper.rs +++ b/storage/aptosdb/src/test_helper.rs @@ -3,7 +3,9 @@ ///! This module provides reusable helpers in tests. use super::*; +use crate::jellyfish_merkle_node::JellyfishMerkleNodeSchema; use aptos_crypto::hash::{CryptoHash, EventAccumulatorHasher, TransactionAccumulatorHasher}; +use aptos_jellyfish_merkle::node_type::{Node, NodeKey}; use aptos_temppath::TempPath; use aptos_types::{ ledger_info::{LedgerInfo, LedgerInfoWithSignatures}, @@ -605,6 +607,16 @@ pub fn put_transaction_info(db: &AptosDB, version: Version, txn_info: &Transacti db.db.write_schemas(cs.batch).unwrap(); } +pub fn put_as_state_root(db: &AptosDB, version: Version, key: StateKey, value: StateValue) { + db.db + .put::( + &NodeKey::new_empty_path(version), + &Node::new_leaf(key.hash(), StateKeyAndValue::new(key, value)), + ) + .unwrap(); + db.state_store.set_latest_version(version); +} + pub fn test_sync_transactions_impl( input: Vec<(Vec, LedgerInfoWithSignatures)>, ) { diff --git a/storage/backup/backup-cli/src/backup_types/state_snapshot/tests.rs b/storage/backup/backup-cli/src/backup_types/state_snapshot/tests.rs index e42f9abece64d..080ce03e4234b 100644 --- a/storage/backup/backup-cli/src/backup_types/state_snapshot/tests.rs +++ b/storage/backup/backup-cli/src/backup_types/state_snapshot/tests.rs @@ -31,7 +31,7 @@ fn end_to_end() { let latest_tree_state = src_db.get_latest_tree_state().unwrap(); let version = latest_tree_state.num_transactions - 1; - let state_root_hash = latest_tree_state.state_root_hash; + let state_root_hash = latest_tree_state.state_checkpoint_hash; let (rt, port) = start_local_backup_service(src_db); let client = Arc::new(BackupServiceClient::new(format!( @@ -78,7 +78,10 @@ fn end_to_end() { let tgt_db = AptosDB::new_for_test(&tgt_db_dir); assert_eq!( - tgt_db.get_latest_tree_state().unwrap().state_root_hash, + tgt_db + .get_latest_tree_state() + .unwrap() + .state_checkpoint_hash, state_root_hash, ); diff --git a/storage/backup/backup-cli/src/backup_types/transaction/restore.rs b/storage/backup/backup-cli/src/backup_types/transaction/restore.rs index 61d8dac7bd9b8..b1e7f8217a845 100644 --- a/storage/backup/backup-cli/src/backup_types/transaction/restore.rs +++ b/storage/backup/backup-cli/src/backup_types/transaction/restore.rs @@ -410,7 +410,7 @@ impl TransactionRestoreBatchController { let first_version = self.replay_from_version.unwrap(); let db = DbReaderWriter::from_arc(Arc::clone(&restore_handler.aptosdb)); let persisted_view = restore_handler - .get_tree_state(first_version)? + .get_tree_state(first_version.checked_sub(1))? .into_ledger_view(&db.reader)?; let chunk_replayer = Arc::new(ChunkExecutor::::new_with_view(db, persisted_view)); diff --git a/storage/storage-interface/src/lib.rs b/storage/storage-interface/src/lib.rs index 020fb6124eb2f..33080d71388e1 100644 --- a/storage/storage-interface/src/lib.rs +++ b/storage/storage-interface/src/lib.rs @@ -30,6 +30,7 @@ use aptos_types::{ AccountTransactionsWithProof, TransactionInfo, TransactionListWithProof, TransactionOutputListWithProof, TransactionToCommit, TransactionWithProof, Version, }, + write_set::WriteSet, }; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, sync::Arc}; @@ -77,7 +78,8 @@ impl StartupInfo { let committed_tree_state = TreeState { num_transactions: 0, ledger_frozen_subtree_hashes: Vec::new(), - state_root_hash: *SPARSE_MERKLE_PLACEHOLDER_HASH, + state_checkpoint_hash: *SPARSE_MERKLE_PLACEHOLDER_HASH, + write_sets_after_checkpoint: Vec::new(), }; let synced_tree_state = None; @@ -109,26 +111,52 @@ impl StartupInfo { pub struct TreeState { pub num_transactions: LeafCount, pub ledger_frozen_subtree_hashes: Vec, - pub state_root_hash: HashValue, + /// Root hash of the state checkpoint (global sparse merkle tree). + pub state_checkpoint_hash: HashValue, + /// The state checkpoint can be at or before the latest version, if the latter case, this is + /// the write sets between the state checkpoint and the latest version. Applying this to the + /// state checkpoint results in a in-mem latest world state. + /// N.b the latest version minus `write_sets_after_checkpoint.len()` is the state checkpoint + /// version. + pub write_sets_after_checkpoint: Vec, } impl TreeState { pub fn new( num_transactions: LeafCount, ledger_frozen_subtree_hashes: Vec, - account_state_root_hash: HashValue, + state_checkpoint_hash: HashValue, + write_sets_after_checkpoint: Vec, ) -> Self { Self { num_transactions, ledger_frozen_subtree_hashes, - state_root_hash: account_state_root_hash, + state_checkpoint_hash, + write_sets_after_checkpoint, } } + pub fn new_at_state_checkpoint( + num_transactions: LeafCount, + ledger_frozen_subtree_hashes: Vec, + state_root_hash: HashValue, + ) -> Self { + Self { + num_transactions, + ledger_frozen_subtree_hashes, + state_checkpoint_hash: state_root_hash, + write_sets_after_checkpoint: Vec::new(), + } + } + + pub fn new_empty() -> Self { + Self::new_at_state_checkpoint(0, Vec::new(), *SPARSE_MERKLE_PLACEHOLDER_HASH) + } + pub fn describe(&self) -> &'static str { if self.num_transactions != 0 { "DB has been bootstrapped." - } else if self.state_root_hash != *SPARSE_MERKLE_PLACEHOLDER_HASH { + } else if self.state_checkpoint_hash != *SPARSE_MERKLE_PLACEHOLDER_HASH { "DB has no transaction, but a non-empty pre-genesis state." } else { "DB is empty, has no transaction or state." diff --git a/storage/storage-interface/src/verified_state_view.rs b/storage/storage-interface/src/verified_state_view.rs index 543c6e30713ba..c139b9867131b 100644 --- a/storage/storage-interface/src/verified_state_view.rs +++ b/storage/storage-interface/src/verified_state_view.rs @@ -12,10 +12,14 @@ use aptos_types::{ proof::SparseMerkleProof, state_store::{state_key::StateKey, state_value::StateValue}, transaction::{Version, PRE_GENESIS_VERSION}, + write_set::WriteSet, }; use parking_lot::RwLock; use scratchpad::{FrozenSparseMerkleTree, SparseMerkleTree, StateStoreStatus}; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; /// `VerifiedStateView` is like a snapshot of the global state comprised of state view at two /// levels, persistent storage and memory. @@ -112,6 +116,16 @@ impl VerifiedStateView { } } + pub fn prime_cache_by_write_set(&self, write_sets: &[&WriteSet]) -> Result<()> { + write_sets + .iter() + .flat_map(|write_set| write_set.iter()) + .map(|(key, _)| key) + .collect::>() + .into_iter() + .try_for_each(|key| self.get_state_value(key).map(|_| ())) + } + pub fn into_state_cache(self) -> StateCache { StateCache { frozen_base: self.speculative_state, diff --git a/storage/storage-service/src/storage_service_test.rs b/storage/storage-service/src/storage_service_test.rs index 2a08d03f49cfb..ab1c09fb6ffa8 100644 --- a/storage/storage-service/src/storage_service_test.rs +++ b/storage/storage-service/src/storage_service_test.rs @@ -75,7 +75,7 @@ proptest! { prop_assert_eq!(&Some(blob), &state_with_proof.0); prop_assert!(state_with_proof.1 .verify( - startup_info.committed_tree_state.state_root_hash, + startup_info.committed_tree_state.state_checkpoint_hash, address.hash(), state_with_proof.0.as_ref() ) diff --git a/testsuite/smoke-test/src/storage.rs b/testsuite/smoke-test/src/storage.rs index 4648a6d0f50b6..996afc281d49b 100644 --- a/testsuite/smoke-test/src/storage.rs +++ b/testsuite/smoke-test/src/storage.rs @@ -22,6 +22,11 @@ use std::{ time::{Duration, Instant}, }; +// TODO(aldenhu): +// A DB restored from a backup can have no checkpoint before the "committed version", which is +// deemed to exist only because there's the last epoch ending ledger info. +// Needs to address separately. +#[ignore] #[tokio::test] async fn test_db_restore() { // pre-build tools