diff --git a/clarity/src/vm/database/clarity_store.rs b/clarity/src/vm/database/clarity_store.rs index e1d6dacaee..90f28cacbc 100644 --- a/clarity/src/vm/database/clarity_store.rs +++ b/clarity/src/vm/database/clarity_store.rs @@ -72,6 +72,7 @@ pub trait ClarityBackingStore { /// used to implement time-shifted evaluation. /// returns the previous block header hash on success fn set_block_hash(&mut self, bhh: StacksBlockId) -> Result; + fn get_block_hash(&self) -> StacksBlockId; /// Is None if `block_height` >= the "currently" under construction Stacks block height. fn get_block_at_height(&mut self, height: u32) -> Option; @@ -141,6 +142,15 @@ pub trait ClarityBackingStore { } } +pub trait ClarityBackingStoreTransaction: ClarityBackingStore { + fn rollback_block(self: Box); + fn rollback_unconfirmed(self: Box) -> Result<()>; + fn commit_to(self: Box, final_bhh: &StacksBlockId) -> Result<()>; + fn commit_unconfirmed(self: Box); + fn commit_mined_block(self: Box, will_move_to: &StacksBlockId) -> Result<()>; + fn seal(&mut self) -> TrieHash; +} + // TODO: Figure out where this belongs pub fn make_contract_hash_key(contract: &QualifiedContractIdentifier) -> String { format!("clarity-contract::{contract}") @@ -202,6 +212,10 @@ impl ClarityBackingStore for NullBackingStore { panic!("NullBackingStore can't set block hash") } + fn get_block_hash(&self) -> StacksBlockId { + panic!("NullBackingStore can't get block hash") + } + fn get_data(&mut self, _key: &str) -> Result> { panic!("NullBackingStore can't retrieve data") } diff --git a/clarity/src/vm/database/mod.rs b/clarity/src/vm/database/mod.rs index cee4cbe00c..656dc7dae3 100644 --- a/clarity/src/vm/database/mod.rs +++ b/clarity/src/vm/database/mod.rs @@ -20,7 +20,9 @@ pub use self::clarity_db::{ BurnStateDB, ClarityDatabase, HeadersDB, StoreType, NULL_BURN_STATE_DB, NULL_HEADER_DB, STORE_CONTRACT_SRC_INTERFACE, }; -pub use self::clarity_store::{ClarityBackingStore, SpecialCaseHandler}; +pub use self::clarity_store::{ + ClarityBackingStore, ClarityBackingStoreTransaction, SpecialCaseHandler, +}; pub use self::key_value_wrapper::{RollbackWrapper, RollbackWrapperPersistedLog}; #[cfg(feature = "rusqlite")] pub use self::sqlite::SqliteConnection; diff --git a/clarity/src/vm/database/sqlite.rs b/clarity/src/vm/database/sqlite.rs index 7264a533bb..784f5fb000 100644 --- a/clarity/src/vm/database/sqlite.rs +++ b/clarity/src/vm/database/sqlite.rs @@ -316,6 +316,10 @@ impl ClarityBackingStore for MemoryBackingStore { Err(RuntimeErrorType::UnknownBlockHeaderHash(BlockHeaderHash(bhh.0)).into()) } + fn get_block_hash(&self) -> StacksBlockId { + panic!("MemoryBackingStore can't get block hash") + } + fn get_data(&mut self, key: &str) -> Result> { SqliteConnection::get(self.get_side_store(), key) } diff --git a/stackslib/src/clarity_cli.rs b/stackslib/src/clarity_cli.rs index 6b714771b2..bb033972a0 100644 --- a/stackslib/src/clarity_cli.rs +++ b/stackslib/src/clarity_cli.rs @@ -20,6 +20,7 @@ use std::path::PathBuf; use std::{fs, io}; use clarity::vm::coverage::CoverageReporter; +use clarity::vm::database::ClarityBackingStoreTransaction; use lazy_static::lazy_static; use rand::Rng; use rusqlite::{Connection, OpenFlags}; @@ -352,6 +353,7 @@ where let (headers_return, result) = { let marf_tx = marf_kv.begin(&from, &to); let (headers_return, marf_return, result) = f(headers_db, marf_tx); + let marf_return: Box = Box::new(marf_return); marf_return .commit_to(&to) .expect("FATAL: failed to commit block"); @@ -374,6 +376,7 @@ where let marf_tx = marf_kv.begin(&from, &to); let (marf_return, result) = f(marf_tx); + let marf_return: Box = Box::new(marf_return); marf_return.rollback_block(); result } @@ -389,6 +392,7 @@ where let marf_tx = marf_kv.begin(&from, &to); let (marf_return, result) = f(marf_tx); + let marf_return: Box = Box::new(marf_return); marf_return.rollback_block(); result } diff --git a/stackslib/src/clarity_vm/clarity.rs b/stackslib/src/clarity_vm/clarity.rs index f4aa9a051e..de704b1946 100644 --- a/stackslib/src/clarity_vm/clarity.rs +++ b/stackslib/src/clarity_vm/clarity.rs @@ -25,8 +25,8 @@ pub use clarity::vm::clarity::{ClarityConnection, Error}; use clarity::vm::contexts::{AssetMap, OwnedEnvironment}; use clarity::vm::costs::{CostTracker, ExecutionCost, LimitedCostTracker}; use clarity::vm::database::{ - BurnStateDB, ClarityBackingStore, ClarityDatabase, HeadersDB, RollbackWrapper, - RollbackWrapperPersistedLog, STXBalance, NULL_BURN_STATE_DB, NULL_HEADER_DB, + BurnStateDB, ClarityBackingStore, ClarityBackingStoreTransaction, ClarityDatabase, HeadersDB, + RollbackWrapper, RollbackWrapperPersistedLog, STXBalance, NULL_BURN_STATE_DB, NULL_HEADER_DB, }; use clarity::vm::errors::Error as InterpreterError; use clarity::vm::events::{STXEventType, STXMintEventData}; @@ -53,7 +53,7 @@ use crate::chainstate::stacks::{ Error as ChainstateError, StacksMicroblockHeader, StacksTransaction, TransactionPayload, TransactionSmartContract, TransactionVersion, }; -use crate::clarity_vm::database::marf::{MarfedKV, ReadOnlyMarfStore, WritableMarfStore}; +use crate::clarity_vm::database::marf::MarfedKV; use crate::core::{StacksEpoch, StacksEpochId, FIRST_STACKS_BLOCK_ID, GENESIS_EPOCH}; use crate::util_lib::boot::{boot_code_acc, boot_code_addr, boot_code_id, boot_code_tx_auth}; use crate::util_lib::db::Error as DatabaseError; @@ -101,7 +101,7 @@ pub struct ClarityInstance { /// issuring event dispatches, before the Clarity database commits. /// pub struct PreCommitClarityBlock<'a> { - datastore: WritableMarfStore<'a>, + datastore: Box, commit_to: StacksBlockId, } @@ -109,7 +109,7 @@ pub struct PreCommitClarityBlock<'a> { /// A high-level interface for Clarity VM interactions within a single block. /// pub struct ClarityBlockConnection<'a, 'b> { - datastore: WritableMarfStore<'a>, + datastore: Box, header_db: &'b dyn HeadersDB, burn_state_db: &'b dyn BurnStateDB, cost_track: Option, @@ -160,7 +160,7 @@ impl<'a, 'b> ClarityTransactionConnection<'a, 'b> { } pub struct ClarityReadOnlyConnection<'a> { - datastore: ReadOnlyMarfStore<'a>, + datastore: Box, header_db: &'a dyn HeadersDB, burn_state_db: &'a dyn BurnStateDB, epoch: StacksEpochId, @@ -196,13 +196,13 @@ macro_rules! using { impl ClarityBlockConnection<'_, '_> { #[cfg(test)] pub fn new_test_conn<'a, 'b>( - datastore: WritableMarfStore<'a>, + datastore: super::database::marf::WritableMarfStore<'a>, header_db: &'b dyn HeadersDB, burn_state_db: &'b dyn BurnStateDB, epoch: StacksEpochId, ) -> ClarityBlockConnection<'a, 'b> { ClarityBlockConnection { - datastore, + datastore: Box::new(datastore), header_db, burn_state_db, cost_track: Some(LimitedCostTracker::new_free()), @@ -251,7 +251,7 @@ impl ClarityBlockConnection<'_, '_> { &mut self, burn_state_db: &dyn BurnStateDB, ) -> Result { - let mut db = self.datastore.as_clarity_db(self.header_db, burn_state_db); + let mut db = ClarityDatabase::new(self.datastore.as_mut(), self.header_db, burn_state_db); // NOTE: the begin/roll_back shouldn't be necessary with how this gets used in practice, // but is put here defensively. db.begin(); @@ -328,7 +328,7 @@ impl ClarityInstance { }; ClarityBlockConnection { - datastore, + datastore: Box::new(datastore), header_db, burn_state_db, cost_track, @@ -352,7 +352,7 @@ impl ClarityInstance { let cost_track = Some(LimitedCostTracker::new_free()); ClarityBlockConnection { - datastore, + datastore: Box::new(datastore), header_db, burn_state_db, cost_track, @@ -378,7 +378,7 @@ impl ClarityInstance { let cost_track = Some(LimitedCostTracker::new_free()); let mut conn = ClarityBlockConnection { - datastore: writable, + datastore: Box::new(writable), header_db, burn_state_db, cost_track, @@ -477,7 +477,7 @@ impl ClarityInstance { let cost_track = Some(LimitedCostTracker::new_free()); let mut conn = ClarityBlockConnection { - datastore: writable, + datastore: Box::new(writable), header_db, burn_state_db, cost_track, @@ -559,6 +559,7 @@ impl ClarityInstance { pub fn drop_unconfirmed_state(&mut self, block: &StacksBlockId) -> Result<(), Error> { let datastore = self.datastore.begin_unconfirmed(block); + let datastore: Box = Box::new(datastore); datastore.rollback_unconfirmed()?; Ok(()) } @@ -588,7 +589,7 @@ impl ClarityInstance { }; ClarityBlockConnection { - datastore, + datastore: Box::new(datastore), header_db, burn_state_db, cost_track, @@ -628,7 +629,7 @@ impl ClarityInstance { }?; Ok(ClarityReadOnlyConnection { - datastore, + datastore: Box::new(datastore), header_db, burn_state_db, epoch, @@ -677,7 +678,8 @@ impl ClarityConnection for ClarityBlockConnection<'_, '_> { where F: FnOnce(ClarityDatabase) -> (R, ClarityDatabase), { - let mut db = ClarityDatabase::new(&mut self.datastore, self.header_db, self.burn_state_db); + let mut db = + ClarityDatabase::new(self.datastore.as_mut(), self.header_db, self.burn_state_db); db.begin(); let (result, mut db) = to_do(db); db.roll_back() @@ -689,7 +691,7 @@ impl ClarityConnection for ClarityBlockConnection<'_, '_> { where F: FnOnce(&mut AnalysisDatabase) -> R, { - let mut db = AnalysisDatabase::new(&mut self.datastore); + let mut db = AnalysisDatabase::new(self.datastore.as_mut()); db.begin(); let result = to_do(&mut db); db.roll_back() @@ -708,9 +710,8 @@ impl ClarityConnection for ClarityReadOnlyConnection<'_> { where F: FnOnce(ClarityDatabase) -> (R, ClarityDatabase), { - let mut db = self - .datastore - .as_clarity_db(self.header_db, self.burn_state_db); + let mut db = + ClarityDatabase::new(self.datastore.as_mut(), self.header_db, self.burn_state_db); db.begin(); let (result, mut db) = to_do(db); db.roll_back() @@ -722,7 +723,7 @@ impl ClarityConnection for ClarityReadOnlyConnection<'_> { where F: FnOnce(&mut AnalysisDatabase) -> R, { - let mut db = self.datastore.as_analysis_db(); + let mut db = AnalysisDatabase::new(self.datastore.as_mut()); db.begin(); let result = to_do(&mut db); db.roll_back() @@ -773,7 +774,8 @@ impl<'a> ClarityBlockConnection<'a, '_> { #[cfg(test)] pub fn commit_block(self) -> LimitedCostTracker { debug!("Commit Clarity datastore"); - self.datastore.test_commit(); + let bhh = self.datastore.get_block_hash(); + self.datastore.commit_to(&bhh).unwrap(); self.cost_track.unwrap() } @@ -1709,7 +1711,7 @@ impl<'a> ClarityBlockConnection<'a, '_> { pub fn start_transaction_processing(&mut self) -> ClarityTransactionConnection<'_, '_> { ClarityTransactionConnection::new( - &mut self.datastore, + self.datastore.as_mut(), self.header_db, self.burn_state_db, &mut self.cost_track, @@ -1761,7 +1763,7 @@ impl<'a> ClarityBlockConnection<'a, '_> { self.datastore.seal() } - pub fn destruct(self) -> WritableMarfStore<'a> { + pub fn destruct(self) -> Box { self.datastore } diff --git a/stackslib/src/clarity_vm/database/marf.rs b/stackslib/src/clarity_vm/database/marf.rs index 80a8534af8..43d8a1cfc0 100644 --- a/stackslib/src/clarity_vm/database/marf.rs +++ b/stackslib/src/clarity_vm/database/marf.rs @@ -8,8 +8,8 @@ use clarity::vm::database::sqlite::{ sqlite_insert_metadata, }; use clarity::vm::database::{ - BurnStateDB, ClarityBackingStore, ClarityDatabase, HeadersDB, SpecialCaseHandler, - SqliteConnection, + BurnStateDB, ClarityBackingStore, ClarityBackingStoreTransaction, ClarityDatabase, HeadersDB, + SpecialCaseHandler, SqliteConnection, }; use clarity::vm::errors::{ IncomparableError, InterpreterError, InterpreterResult, RuntimeErrorType, @@ -300,6 +300,10 @@ impl ClarityBackingStore for ReadOnlyMarfStore<'_> { Some(&handle_contract_call_special_cases) } + fn get_block_hash(&self) -> StacksBlockId { + self.chain_tip.clone() + } + /// Sets the chain tip at which queries will happen. Used for `(at-block ..)` fn set_block_hash(&mut self, bhh: StacksBlockId) -> InterpreterResult { self.marf @@ -549,73 +553,19 @@ impl WritableMarfStore<'_> { AnalysisDatabase::new(self) } - pub fn rollback_block(self) { - self.marf.drop_current(); - } - - pub fn rollback_unconfirmed(self) -> InterpreterResult<()> { - debug!("Drop unconfirmed MARF trie {}", &self.chain_tip); - SqliteConnection::drop_metadata(self.marf.sqlite_tx(), &self.chain_tip)?; - self.marf.drop_unconfirmed(); - Ok(()) - } - - pub fn commit_to(self, final_bhh: &StacksBlockId) -> InterpreterResult<()> { - debug!("commit_to({})", final_bhh); - SqliteConnection::commit_metadata_to(self.marf.sqlite_tx(), &self.chain_tip, final_bhh)?; - - let _ = self.marf.commit_to(final_bhh).map_err(|e| { - error!("Failed to commit to MARF block {}: {:?}", &final_bhh, &e); - InterpreterError::Expect("Failed to commit to MARF block".into()) - })?; - Ok(()) - } - #[cfg(test)] pub fn test_commit(self) { let bhh = self.chain_tip.clone(); - self.commit_to(&bhh).unwrap(); - } - - pub fn commit_unconfirmed(self) { - debug!("commit_unconfirmed()"); - // NOTE: Can omit commit_metadata_to, since the block header hash won't change - // commit_metadata_to(&self.chain_tip, final_bhh); - self.marf - .commit() - .expect("ERROR: Failed to commit MARF block"); - } - - // This is used by miners - // so that the block validation and processing logic doesn't - // reprocess the same data as if it were already loaded - pub fn commit_mined_block(self, will_move_to: &StacksBlockId) -> InterpreterResult<()> { - debug!( - "commit_mined_block: ({}->{})", - &self.chain_tip, will_move_to - ); - // rollback the side_store - // the side_store shouldn't commit data for blocks that won't be - // included in the processed chainstate (like a block constructed during mining) - // _if_ for some reason, we do want to be able to access that mined chain state in the future, - // we should probably commit the data to a different table which does not have uniqueness constraints. - SqliteConnection::drop_metadata(self.marf.sqlite_tx(), &self.chain_tip)?; - let _ = self.marf.commit_mined(will_move_to).map_err(|e| { - error!( - "Failed to commit to mined MARF block {}: {:?}", - &will_move_to, &e - ); - InterpreterError::Expect("Failed to commit to MARF block".into()) - })?; - Ok(()) - } - - pub fn seal(&mut self) -> TrieHash { - self.marf.seal().expect("FATAL: failed to .seal() MARF") + let store: Box = Box::new(self); + store.commit_to(&bhh).unwrap(); } } impl ClarityBackingStore for WritableMarfStore<'_> { + fn get_block_hash(&self) -> StacksBlockId { + self.chain_tip.clone() + } + fn set_block_hash(&mut self, bhh: StacksBlockId) -> InterpreterResult { self.marf .check_ancestor_block_hash(&bhh) @@ -864,3 +814,64 @@ impl ClarityBackingStore for WritableMarfStore<'_> { sqlite_get_metadata_manual(self, at_height, contract, key) } } + +impl ClarityBackingStoreTransaction for WritableMarfStore<'_> { + fn rollback_block(self: Box) { + self.marf.drop_current(); + } + + fn rollback_unconfirmed(self: Box) -> InterpreterResult<()> { + debug!("Drop unconfirmed MARF trie {}", &self.chain_tip); + SqliteConnection::drop_metadata(self.marf.sqlite_tx(), &self.chain_tip)?; + self.marf.drop_unconfirmed(); + Ok(()) + } + + fn commit_to(self: Box, final_bhh: &StacksBlockId) -> InterpreterResult<()> { + debug!("commit_to({})", final_bhh); + SqliteConnection::commit_metadata_to(self.marf.sqlite_tx(), &self.chain_tip, final_bhh)?; + + let _ = self.marf.commit_to(final_bhh).map_err(|e| { + error!("Failed to commit to MARF block {}: {:?}", &final_bhh, &e); + InterpreterError::Expect("Failed to commit to MARF block".into()) + })?; + Ok(()) + } + + fn commit_unconfirmed(self: Box) { + debug!("commit_unconfirmed()"); + // NOTE: Can omit commit_metadata_to, since the block header hash won't change + // commit_metadata_to(&self.chain_tip, final_bhh); + self.marf + .commit() + .expect("ERROR: Failed to commit MARF block"); + } + + // This is used by miners + // so that the block validation and processing logic doesn't + // reprocess the same data as if it were already loaded + fn commit_mined_block(self: Box, will_move_to: &StacksBlockId) -> InterpreterResult<()> { + debug!( + "commit_mined_block: ({}->{})", + &self.chain_tip, will_move_to + ); + // rollback the side_store + // the side_store shouldn't commit data for blocks that won't be + // included in the processed chainstate (like a block constructed during mining) + // _if_ for some reason, we do want to be able to access that mined chain state in the future, + // we should probably commit the data to a different table which does not have uniqueness constraints. + SqliteConnection::drop_metadata(self.marf.sqlite_tx(), &self.chain_tip)?; + let _ = self.marf.commit_mined(will_move_to).map_err(|e| { + error!( + "Failed to commit to mined MARF block {}: {:?}", + &will_move_to, &e + ); + InterpreterError::Expect("Failed to commit to MARF block".into()) + })?; + Ok(()) + } + + fn seal(&mut self) -> TrieHash { + self.marf.seal().expect("FATAL: failed to .seal() MARF") + } +} diff --git a/stackslib/src/clarity_vm/database/mod.rs b/stackslib/src/clarity_vm/database/mod.rs index 966a422ba5..0f0aacf107 100644 --- a/stackslib/src/clarity_vm/database/mod.rs +++ b/stackslib/src/clarity_vm/database/mod.rs @@ -1222,6 +1222,10 @@ impl ClarityBackingStore for MemoryBackingStore { Err(RuntimeErrorType::UnknownBlockHeaderHash(BlockHeaderHash(bhh.0)).into()) } + fn get_block_hash(&self) -> StacksBlockId { + panic!("MemoryBackingStore can't get block hash") + } + fn get_data(&mut self, key: &str) -> InterpreterResult> { SqliteConnection::get(self.get_side_store(), key) }