From f10104003e83196b5a8ba5a88e1ce5b05e43f219 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 5 Jul 2024 17:50:13 +0200 Subject: [PATCH 1/5] persistence: add transactional model --- src/persistence/index.rs | 21 +++++++++- src/persistence/memory.rs | 54 +++++++++++++++++++++++- src/persistence/mod.rs | 10 +++++ src/persistence/stash.rs | 22 +++++++++- src/persistence/state.rs | 3 +- src/persistence/stock.rs | 86 +++++++++++++++++++++++++++------------ 6 files changed, 166 insertions(+), 30 deletions(-) diff --git a/src/persistence/index.rs b/src/persistence/index.rs index 187cb10b..fc49f0e1 100644 --- a/src/persistence/index.rs +++ b/src/persistence/index.rs @@ -30,6 +30,7 @@ use rgb::{ }; use crate::containers::{BundledWitness, Consignment, ToWitnessId}; +use crate::persistence::StoreTransaction; use crate::SecretSeal; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -330,6 +331,24 @@ impl Index

{ } } +impl StoreTransaction for Index

{ + type TransactionErr = IndexError

; + + fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.provider + .begin_transaction() + .map_err(IndexError::WriteProvider) + } + + fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.provider + .commit_transaction() + .map_err(IndexError::WriteProvider) + } + + fn rollback_transaction(&mut self) { self.provider.rollback_transaction() } +} + pub trait IndexProvider: Debug + IndexReadProvider + IndexWriteProvider {} pub trait IndexReadProvider { @@ -364,7 +383,7 @@ pub trait IndexReadProvider { ) -> Result<(XWitnessId, ContractId), IndexReadError>; } -pub trait IndexWriteProvider { +pub trait IndexWriteProvider: StoreTransaction { type Error: Clone + Eq + Error; fn register_contract(&mut self, contract_id: ContractId) -> Result; diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 2a2e9005..0ef64bb6 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -41,7 +41,7 @@ use super::{ ContractIfaceError, IndexInconsistency, IndexProvider, IndexReadError, IndexReadProvider, IndexWriteError, IndexWriteProvider, SchemaIfaces, StashInconsistency, StashProvider, StashProviderError, StashReadProvider, StashWriteProvider, StateProvider, StateReadProvider, - StateUpdateError, StateWriteProvider, + StateUpdateError, StateWriteProvider, StoreTransaction, }; use crate::containers::{ AnchorSet, ContentId, ContentRef, ContentSigs, SealWitness, SigBlob, Supplement, TrustLevel, @@ -60,6 +60,8 @@ use crate::LIB_NAME_RGB_STD; #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct MemStash { + dirty: bool, + schemata: TinyOrdMap, ifaces: TinyOrdMap, geneses: TinyOrdMap, @@ -82,6 +84,22 @@ impl MemStash { pub fn new() -> Self { MemStash::default() } } +impl StoreTransaction for MemStash { + type TransactionErr = confinement::Error; + + fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.dirty = true; + Ok(()) + } + + fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { + // We do not do anything here since we do not actually save anything + Ok(()) + } + + fn rollback_transaction(&mut self) { unreachable!() } +} + impl StashProvider for MemStash {} impl StashReadProvider for MemStash { @@ -404,6 +422,7 @@ impl From for StateUpdateError { #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct MemState { + dirty: bool, history: TinyOrdMap, } @@ -414,6 +433,22 @@ impl MemState { pub fn new() -> Self { MemState::default() } } +impl StoreTransaction for MemState { + type TransactionErr = confinement::Error; + + fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.dirty = true; + Ok(()) + } + + fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { + // We do not do anything here since we do not actually save anything + Ok(()) + } + + fn rollback_transaction(&mut self) { unreachable!() } +} + impl StateProvider for MemState {} impl StateReadProvider for MemState { @@ -486,6 +521,7 @@ pub struct ContractIndex { #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct MemIndex { + dirty: bool, op_bundle_index: MediumOrdMap, bundle_contract_index: MediumOrdMap, bundle_witness_index: MediumOrdMap, @@ -500,6 +536,22 @@ impl MemIndex { pub fn new() -> Self { MemIndex::default() } } +impl StoreTransaction for MemIndex { + type TransactionErr = confinement::Error; + + fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.dirty = true; + Ok(()) + } + + fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { + // We do not do anything here since we do not actually save anything + Ok(()) + } + + fn rollback_transaction(&mut self) { unreachable!() } +} + impl IndexProvider for MemIndex {} impl IndexReadProvider for MemIndex { diff --git a/src/persistence/mod.rs b/src/persistence/mod.rs index c56241f7..3c1421fd 100644 --- a/src/persistence/mod.rs +++ b/src/persistence/mod.rs @@ -55,3 +55,13 @@ pub use stock::{ ComposeError, ConsignError, ContractIfaceError, FasciaError, InputError as StockInputError, Stock, StockError, StockErrorAll, StockErrorMem, }; + +pub trait StoreTransaction { + type TransactionErr: std::error::Error; + + fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr>; + + fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr>; + + fn rollback_transaction(&mut self); +} diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 45becb2a..23a97cbf 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -45,7 +45,7 @@ use crate::containers::{ use crate::interface::{ ContractBuilder, Iface, IfaceClass, IfaceId, IfaceImpl, IfaceRef, TransitionBuilder, }; -use crate::persistence::ContractIfaceError; +use crate::persistence::{ContractIfaceError, StoreTransaction}; use crate::{MergeReveal, MergeRevealError, SecretSeal, LIB_NAME_RGB_STD}; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -595,6 +595,24 @@ impl Stash

{ } } +impl StoreTransaction for Stash

{ + type TransactionErr = StashError

; + + fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.provider + .begin_transaction() + .map_err(StashError::WriteProvider) + } + + fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { + self.provider + .commit_transaction() + .map_err(StashError::WriteProvider) + } + + fn rollback_transaction(&mut self) { self.provider.rollback_transaction() } +} + pub trait StashProvider: Debug + StashReadProvider + StashWriteProvider {} pub trait StashReadProvider { @@ -650,7 +668,7 @@ pub trait StashReadProvider { fn secret_seals(&self) -> Result>, Self::Error>; } -pub trait StashWriteProvider { +pub trait StashWriteProvider: StoreTransaction { type Error: Clone + Eq + Error; fn replace_schema(&mut self, schema: Schema) -> Result; diff --git a/src/persistence/state.rs b/src/persistence/state.rs index f791ab02..95a94849 100644 --- a/src/persistence/state.rs +++ b/src/persistence/state.rs @@ -26,6 +26,7 @@ use invoice::Amount; use rgb::{AssetTag, BlindingFactor, ContractHistory, ContractId, DataState}; use crate::interface::AttachedState; +use crate::persistence::StoreTransaction; use crate::resolvers::ResolveHeight; #[derive(Clone, PartialEq, Eq, Debug, Display, Error)] @@ -73,7 +74,7 @@ pub trait StateReadProvider { ) -> Result, Self::Error>; } -pub trait StateWriteProvider { +pub trait StateWriteProvider: StoreTransaction { type Error: Clone + Eq + Error; fn create_or_update_state( diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index ff479cfc..71a5d8e8 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -44,7 +44,7 @@ use super::{ Index, IndexError, IndexInconsistency, IndexProvider, IndexReadProvider, IndexWriteProvider, MemIndex, MemStash, MemState, PersistedState, SchemaIfaces, Stash, StashDataError, StashError, StashInconsistency, StashProvider, StashReadProvider, StashWriteProvider, StateProvider, - StateReadProvider, StateUpdateError, StateWriteProvider, + StateReadProvider, StateUpdateError, StateWriteProvider, StoreTransaction, }; use crate::containers::{ AnchorSet, AnchoredBundles, Batch, BuilderSeal, BundledWitness, Consignment, ContainerVer, @@ -1057,9 +1057,42 @@ impl Stock { Ok(Batch { main, blanks }) } + fn store_transaction( + &mut self, + f: impl FnOnce(&mut Stash, &mut H, &mut Index

) -> Result<(), StockError>, + ) -> Result<(), StockError> { + self.state + .begin_transaction() + .map_err(StockError::StateWrite)?; + self.stash + .begin_transaction() + .inspect_err(|_| self.stash.rollback_transaction())?; + self.index.begin_transaction().inspect_err(|_| { + self.state.rollback_transaction(); + self.stash.rollback_transaction(); + })?; + f(&mut self.stash, &mut self.state, &mut self.index)?; + self.index + .commit_transaction() + .map_err(StockError::from) + .and_then(|_| { + self.state + .commit_transaction() + .map_err(StockError::StateWrite) + }) + .and_then(|_| self.stash.commit_transaction().map_err(StockError::from)) + .inspect_err(|_| { + self.state.rollback_transaction(); + self.stash.rollback_transaction(); + self.index.rollback_transaction(); + }) + } + pub fn import_kit(&mut self, kit: ValidKit) -> Result> { let (kit, status) = kit.split(); + self.stash.begin_transaction()?; self.stash.consume_kit(kit)?; + self.stash.commit_transaction()?; Ok(status) } @@ -1088,12 +1121,14 @@ impl Stock { let (mut consignment, status) = consignment.split(); consignment = self.stash.resolve_secrets(consignment)?; - self.state - .create_or_update_state::(contract_id, |history| { + self.store_transaction(move |stash, state, index| { + state.create_or_update_state::(contract_id, |history| { consignment.update_history(history, resolver) })?; - self.index.index_consignment(&consignment)?; - self.stash.consume_consignment(consignment)?; + index.index_consignment(&consignment)?; + stash.consume_consignment(consignment)?; + Ok(()) + })?; Ok(status) } @@ -1110,25 +1145,25 @@ impl Stock { &mut self, fascia: Fascia, ) -> Result<(), StockError> { - let witness_id = fascia.witness_id(); - self.stash - .consume_witness(SealWitness::new(fascia.witness.clone(), fascia.anchor.clone()))?; - - for (contract_id, bundle) in fascia.into_bundles() { - let ids1 = bundle - .known_transitions - .keys() - .copied() - .collect::>(); - let ids2 = bundle.input_map.values().copied().collect::>(); - if !ids1.is_subset(&ids2) { - return Err(FasciaError::InvalidBundle(contract_id, bundle.bundle_id()).into()); - } + self.store_transaction(move |stash, state, index| { + let witness_id = fascia.witness_id(); + stash + .consume_witness(SealWitness::new(fascia.witness.clone(), fascia.anchor.clone()))?; + + for (contract_id, bundle) in fascia.into_bundles() { + let ids1 = bundle + .known_transitions + .keys() + .copied() + .collect::>(); + let ids2 = bundle.input_map.values().copied().collect::>(); + if !ids1.is_subset(&ids2) { + return Err(FasciaError::InvalidBundle(contract_id, bundle.bundle_id()).into()); + } - self.index.index_bundle(contract_id, &bundle, witness_id)?; + index.index_bundle(contract_id, &bundle, witness_id)?; - self.state - .update_state::(contract_id, |history| { + state.update_state::(contract_id, |history| { for transition in bundle.known_transitions.values() { let witness_anchor = WitnessAnchor::from_mempool(witness_id); history.add_transition(transition, witness_anchor); @@ -1136,9 +1171,10 @@ impl Stock { Ok(()) })?; - self.stash.consume_bundle(bundle)?; - } - Ok(()) + stash.consume_bundle(bundle)?; + } + Ok(()) + }) } fn transition(&self, opid: OpId) -> Result<&Transition, StockError> { From c837cdb214677472b9c18ad02e1d0c36a3da1a47 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 5 Jul 2024 18:02:53 +0200 Subject: [PATCH 2/5] persistence: update load/save workflow --- src/persistence/fs.rs | 53 +++++++++++++++++++++++---------------- src/persistence/memory.rs | 34 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/persistence/fs.rs b/src/persistence/fs.rs index 002adf22..22640d1a 100644 --- a/src/persistence/fs.rs +++ b/src/persistence/fs.rs @@ -33,7 +33,7 @@ pub trait LoadFs: Sized { } pub trait StoreFs { - fn store(&self, path: impl AsRef) -> Result<(), SerializeError>; + fn store(&self) -> Result<(), SerializeError>; } impl LoadFs for Stock @@ -58,11 +58,10 @@ where H: StoreFs, I: StoreFs, { - fn store(&self, path: impl AsRef) -> Result<(), SerializeError> { - let path = path.as_ref(); - self.as_stash_provider().store(path)?; - self.as_state_provider().store(path)?; - self.as_index_provider().store(path)?; + fn store(&self) -> Result<(), SerializeError> { + self.as_stash_provider().store()?; + self.as_state_provider().store()?; + self.as_index_provider().store()?; Ok(()) } @@ -72,15 +71,19 @@ impl LoadFs for MemStash { fn load(path: impl AsRef) -> Result { let mut file = path.as_ref().to_owned(); file.push("stash.dat"); - Self::strict_deserialize_from_file::(file) + let mut me = Self::strict_deserialize_from_file::(&file)?; + me.set_filename(file); + Ok(me) } } impl StoreFs for MemStash { - fn store(&self, path: impl AsRef) -> Result<(), SerializeError> { - let mut file = path.as_ref().to_owned(); - file.push("stash.dat"); - self.strict_serialize_to_file::(file) + fn store(&self) -> Result<(), SerializeError> { + if self.is_dirty() { + self.strict_serialize_to_file::(&self.filename()) + } else { + Ok(()) + } } } @@ -88,15 +91,19 @@ impl LoadFs for MemState { fn load(path: impl AsRef) -> Result { let mut file = path.as_ref().to_owned(); file.push("state.dat"); - Self::strict_deserialize_from_file::(file) + let mut me = Self::strict_deserialize_from_file::(&file)?; + me.set_filename(file); + Ok(me) } } impl StoreFs for MemState { - fn store(&self, path: impl AsRef) -> Result<(), SerializeError> { - let mut file = path.as_ref().to_owned(); - file.push("state.dat"); - self.strict_serialize_to_file::(file) + fn store(&self) -> Result<(), SerializeError> { + if self.is_dirty() { + self.strict_serialize_to_file::(&self.filename()) + } else { + Ok(()) + } } } @@ -104,14 +111,18 @@ impl LoadFs for MemIndex { fn load(path: impl AsRef) -> Result { let mut file = path.as_ref().to_owned(); file.push("index.dat"); - Self::strict_deserialize_from_file::(file) + let mut me = Self::strict_deserialize_from_file::(&file)?; + me.set_filename(file); + Ok(me) } } impl StoreFs for MemIndex { - fn store(&self, path: impl AsRef) -> Result<(), SerializeError> { - let mut file = path.as_ref().to_owned(); - file.push("index.dat"); - self.strict_serialize_to_file::(file) + fn store(&self) -> Result<(), SerializeError> { + if self.is_dirty() { + self.strict_serialize_to_file::(&self.filename()) + } else { + Ok(()) + } } } diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 0ef64bb6..c0797cdc 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -21,6 +21,8 @@ use std::collections::BTreeSet; use std::convert::Infallible; +#[cfg(feature = "fs")] +use std::path::{Path, PathBuf}; use aluvm::library::{Lib, LibId}; use amplify::confinement::{ @@ -60,7 +62,11 @@ use crate::LIB_NAME_RGB_STD; #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct MemStash { + #[strict_type(skip)] dirty: bool, + #[cfg(feature = "fs")] + #[strict_type(skip)] + filename: PathBuf, schemata: TinyOrdMap, ifaces: TinyOrdMap, @@ -82,6 +88,12 @@ impl StrictDeserialize for MemStash {} impl MemStash { pub fn new() -> Self { MemStash::default() } + + pub(crate) fn is_dirty(&self) -> bool { self.dirty } + #[cfg(feature = "fs")] + pub(crate) fn filename(&self) -> &Path { &self.filename } + #[cfg(feature = "fs")] + pub(crate) fn set_filename(&mut self, filename: PathBuf) { self.filename = filename } } impl StoreTransaction for MemStash { @@ -422,7 +434,12 @@ impl From for StateUpdateError { #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct MemState { + #[strict_type(skip)] dirty: bool, + #[cfg(feature = "fs")] + #[strict_type(skip)] + filename: PathBuf, + history: TinyOrdMap, } @@ -431,6 +448,12 @@ impl StrictDeserialize for MemState {} impl MemState { pub fn new() -> Self { MemState::default() } + + pub(crate) fn is_dirty(&self) -> bool { self.dirty } + #[cfg(feature = "fs")] + pub(crate) fn filename(&self) -> &Path { &self.filename } + #[cfg(feature = "fs")] + pub(crate) fn set_filename(&mut self, filename: PathBuf) { self.filename = filename } } impl StoreTransaction for MemState { @@ -521,7 +544,12 @@ pub struct ContractIndex { #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] pub struct MemIndex { + #[strict_type(skip)] dirty: bool, + #[cfg(feature = "fs")] + #[strict_type(skip)] + filename: PathBuf, + op_bundle_index: MediumOrdMap, bundle_contract_index: MediumOrdMap, bundle_witness_index: MediumOrdMap, @@ -534,6 +562,12 @@ impl StrictDeserialize for MemIndex {} impl MemIndex { pub fn new() -> Self { MemIndex::default() } + + pub(crate) fn is_dirty(&self) -> bool { self.dirty } + #[cfg(feature = "fs")] + pub(crate) fn filename(&self) -> &Path { &self.filename } + #[cfg(feature = "fs")] + pub(crate) fn set_filename(&mut self, filename: PathBuf) { self.filename = filename } } impl StoreTransaction for MemIndex { From f00e86085e13a342fbd2300e49f3e7f7242cdd9c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 6 Jul 2024 10:43:56 +0200 Subject: [PATCH 3/5] persistence: add API to name stored data --- src/persistence/fs.rs | 109 ++------------------------- src/persistence/index.rs | 3 + src/persistence/memory.rs | 151 ++++++++++++++++++++++++++++++-------- src/persistence/stash.rs | 3 + src/persistence/stock.rs | 77 +++++++++++++++++++ 5 files changed, 211 insertions(+), 132 deletions(-) diff --git a/src/persistence/fs.rs b/src/persistence/fs.rs index 22640d1a..a58283b0 100644 --- a/src/persistence/fs.rs +++ b/src/persistence/fs.rs @@ -19,110 +19,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::path::Path; +use std::path::{Path, PathBuf}; -use amplify::confinement::U32; -use strict_encoding::{DeserializeError, SerializeError, StrictDeserialize, StrictSerialize}; +use strict_encoding::{DeserializeError, SerializeError}; -use crate::persistence::{ - IndexProvider, MemIndex, MemStash, MemState, StashProvider, StateProvider, Stock, -}; +pub trait FsStored: Sized { + fn new(path: impl ToOwned) -> Self; + fn load(path: impl ToOwned) -> Result; -pub trait LoadFs: Sized { - fn load(path: impl AsRef) -> Result; -} + fn is_dirty(&self) -> bool; + fn filename(&self) -> &Path; + fn set_filename(&mut self, filename: impl ToOwned) -> PathBuf; -pub trait StoreFs { fn store(&self) -> Result<(), SerializeError>; } - -impl LoadFs for Stock -where - S: LoadFs, - H: LoadFs, - I: LoadFs, -{ - fn load(path: impl AsRef) -> Result { - let path = path.as_ref(); - let stash = S::load(path)?; - let state = H::load(path)?; - let index = I::load(path)?; - - Ok(Stock::with(stash, state, index)) - } -} - -impl StoreFs for Stock -where - S: StoreFs, - H: StoreFs, - I: StoreFs, -{ - fn store(&self) -> Result<(), SerializeError> { - self.as_stash_provider().store()?; - self.as_state_provider().store()?; - self.as_index_provider().store()?; - - Ok(()) - } -} - -impl LoadFs for MemStash { - fn load(path: impl AsRef) -> Result { - let mut file = path.as_ref().to_owned(); - file.push("stash.dat"); - let mut me = Self::strict_deserialize_from_file::(&file)?; - me.set_filename(file); - Ok(me) - } -} - -impl StoreFs for MemStash { - fn store(&self) -> Result<(), SerializeError> { - if self.is_dirty() { - self.strict_serialize_to_file::(&self.filename()) - } else { - Ok(()) - } - } -} - -impl LoadFs for MemState { - fn load(path: impl AsRef) -> Result { - let mut file = path.as_ref().to_owned(); - file.push("state.dat"); - let mut me = Self::strict_deserialize_from_file::(&file)?; - me.set_filename(file); - Ok(me) - } -} - -impl StoreFs for MemState { - fn store(&self) -> Result<(), SerializeError> { - if self.is_dirty() { - self.strict_serialize_to_file::(&self.filename()) - } else { - Ok(()) - } - } -} - -impl LoadFs for MemIndex { - fn load(path: impl AsRef) -> Result { - let mut file = path.as_ref().to_owned(); - file.push("index.dat"); - let mut me = Self::strict_deserialize_from_file::(&file)?; - me.set_filename(file); - Ok(me) - } -} - -impl StoreFs for MemIndex { - fn store(&self) -> Result<(), SerializeError> { - if self.is_dirty() { - self.strict_serialize_to_file::(&self.filename()) - } else { - Ok(()) - } - } -} diff --git a/src/persistence/index.rs b/src/persistence/index.rs index fc49f0e1..0ad897f2 100644 --- a/src/persistence/index.rs +++ b/src/persistence/index.rs @@ -147,6 +147,9 @@ impl Index

{ #[doc(hidden)] pub fn as_provider(&self) -> &P { &self.provider } + #[doc(hidden)] + pub(super) fn as_provider_mut(&mut self) -> &mut P { &mut self.provider } + pub(super) fn index_consignment( &mut self, consignment: &Consignment, diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index c0797cdc..6f4d267e 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -22,7 +22,7 @@ use std::collections::BTreeSet; use std::convert::Infallible; #[cfg(feature = "fs")] -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use aluvm::library::{Lib, LibId}; use amplify::confinement::{ @@ -86,16 +86,6 @@ pub struct MemStash { impl StrictSerialize for MemStash {} impl StrictDeserialize for MemStash {} -impl MemStash { - pub fn new() -> Self { MemStash::default() } - - pub(crate) fn is_dirty(&self) -> bool { self.dirty } - #[cfg(feature = "fs")] - pub(crate) fn filename(&self) -> &Path { &self.filename } - #[cfg(feature = "fs")] - pub(crate) fn set_filename(&mut self, filename: PathBuf) { self.filename = filename } -} - impl StoreTransaction for MemStash { type TransactionErr = confinement::Error; @@ -446,16 +436,6 @@ pub struct MemState { impl StrictSerialize for MemState {} impl StrictDeserialize for MemState {} -impl MemState { - pub fn new() -> Self { MemState::default() } - - pub(crate) fn is_dirty(&self) -> bool { self.dirty } - #[cfg(feature = "fs")] - pub(crate) fn filename(&self) -> &Path { &self.filename } - #[cfg(feature = "fs")] - pub(crate) fn set_filename(&mut self, filename: PathBuf) { self.filename = filename } -} - impl StoreTransaction for MemState { type TransactionErr = confinement::Error; @@ -560,16 +540,6 @@ pub struct MemIndex { impl StrictSerialize for MemIndex {} impl StrictDeserialize for MemIndex {} -impl MemIndex { - pub fn new() -> Self { MemIndex::default() } - - pub(crate) fn is_dirty(&self) -> bool { self.dirty } - #[cfg(feature = "fs")] - pub(crate) fn filename(&self) -> &Path { &self.filename } - #[cfg(feature = "fs")] - pub(crate) fn set_filename(&mut self, filename: PathBuf) { self.filename = filename } -} - impl StoreTransaction for MemIndex { type TransactionErr = confinement::Error; @@ -830,3 +800,122 @@ impl IndexWriteProvider for MemIndex { Ok(()) } } + +#[cfg(feature = "fs")] +mod fs { + use std::path::{Path, PathBuf}; + + use amplify::confinement::U32; + use strict_encoding::{DeserializeError, SerializeError, StrictDeserialize, StrictSerialize}; + + use crate::persistence::fs::FsStored; + use crate::persistence::{MemIndex, MemStash, MemState}; + + impl FsStored for MemStash { + fn new(filename: impl ToOwned) -> Self { + Self { + dirty: true, + filename: filename.to_owned(), + ..default!() + } + } + + fn load(path: impl ToOwned) -> Result { + let path = path.to_owned(); + let mut me = Self::strict_deserialize_from_file::(&path)?; + me.set_filename(path); + Ok(me) + } + + fn is_dirty(&self) -> bool { self.dirty } + + fn filename(&self) -> &Path { &self.filename } + + fn set_filename(&mut self, filename: impl ToOwned) -> PathBuf { + let prev = self.filename.to_owned(); + self.filename = filename.to_owned(); + self.dirty = self.filename != prev; + prev + } + + fn store(&self) -> Result<(), SerializeError> { + if self.is_dirty() { + self.strict_serialize_to_file::(&self.filename()) + } else { + Ok(()) + } + } + } + + impl FsStored for MemState { + fn new(filename: impl ToOwned) -> Self { + Self { + dirty: true, + filename: filename.to_owned(), + ..default!() + } + } + + fn load(path: impl ToOwned) -> Result { + let path = path.to_owned(); + let mut me = Self::strict_deserialize_from_file::(&path)?; + me.set_filename(path); + Ok(me) + } + + fn is_dirty(&self) -> bool { self.dirty } + + fn filename(&self) -> &Path { &self.filename } + + fn set_filename(&mut self, filename: impl ToOwned) -> PathBuf { + let prev = self.filename.to_owned(); + self.filename = filename.to_owned(); + self.dirty = self.filename != prev; + prev + } + + fn store(&self) -> Result<(), SerializeError> { + if self.is_dirty() { + self.strict_serialize_to_file::(&self.filename()) + } else { + Ok(()) + } + } + } + + impl FsStored for MemIndex { + fn new(filename: impl ToOwned) -> Self { + Self { + dirty: true, + filename: filename.to_owned(), + ..default!() + } + } + + fn load(path: impl ToOwned) -> Result { + let path = path.to_owned(); + let mut me = Self::strict_deserialize_from_file::(&path)?; + me.set_filename(path); + Ok(me) + } + + fn is_dirty(&self) -> bool { self.dirty } + + fn filename(&self) -> &Path { &self.filename } + + fn set_filename(&mut self, filename: impl ToOwned) -> PathBuf { + let prev = self.filename.to_owned(); + self.filename = filename.to_owned(); + self.dirty = self.filename != prev; + prev + } + + fn store(&self) -> Result<(), SerializeError> { + if self.is_dirty() { + self.strict_serialize_to_file::(&self.filename()) + } else { + Ok(()) + } + } + } +} diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 23a97cbf..0382da53 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -205,6 +205,9 @@ impl Stash

{ #[doc(hidden)] pub fn as_provider(&self) -> &P { &self.provider } + #[doc(hidden)] + pub(super) fn as_provider_mut(&mut self) -> &mut P { &mut self.provider } + pub(super) fn ifaces(&self) -> Result + '_, StashError

> { self.provider.ifaces().map_err(StashError::ReadProvider) } diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 71a5d8e8..85004fc0 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -372,6 +372,83 @@ impl Stock { pub fn in_memory() -> Self { Self::default() } } +#[cfg(feature = "fs")] +mod fs { + use std::path::PathBuf; + + use strict_encoding::{DeserializeError, SerializeError}; + + use super::*; + use crate::persistence::fs::FsStored; + + impl Stock + where + S: FsStored, + H: FsStored, + I: FsStored, + { + pub fn new(path: impl ToOwned) -> Self { + let mut filename = path.to_owned(); + filename.push("stash.dat"); + let stash = S::new(filename); + + let mut filename = path.to_owned(); + filename.push("state.dat"); + let state = H::new(filename); + + let mut filename = path.to_owned(); + filename.push("index.dat"); + let index = I::new(filename); + + Stock::with(stash, state, index) + } + + pub fn load(path: impl ToOwned) -> Result { + let mut filename = path.to_owned(); + filename.push("stash.dat"); + let stash = S::load(filename)?; + + let mut filename = path.to_owned(); + filename.push("state.dat"); + let state = H::load(filename)?; + + let mut filename = path.to_owned(); + filename.push("index.dat"); + let index = I::load(filename)?; + + Ok(Stock::with(stash, state, index)) + } + + pub fn is_dirty(&self) -> bool { + self.as_stash_provider().is_dirty() || + self.as_state_provider().is_dirty() || + self.as_index_provider().is_dirty() + } + + pub fn set_path(&mut self, path: impl ToOwned) { + let mut filename = path.to_owned(); + filename.push("stash.dat"); + self.stash.as_provider_mut().set_filename(filename); + + let mut filename = path.to_owned(); + filename.push("state.dat"); + self.state.set_filename(filename); + + let mut filename = path.to_owned(); + filename.push("index.dat"); + self.index.as_provider_mut().set_filename(filename); + } + + pub fn store(&self) -> Result<(), SerializeError> { + self.as_stash_provider().store()?; + self.as_state_provider().store()?; + self.as_index_provider().store()?; + + Ok(()) + } + } +} + impl Stock { pub fn with(stash_provider: S, state_provider: H, index_provider: P) -> Self { Stock { From 413554977b3a2dacee27c3f51cacb8962d1076b1 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 9 Jul 2024 13:58:50 +0200 Subject: [PATCH 4/5] persistance: do save data on store transaction commit --- src/persistence/index.rs | 6 ++++ src/persistence/memory.rs | 59 ++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/persistence/index.rs b/src/persistence/index.rs index 0ad897f2..c0014850 100644 --- a/src/persistence/index.rs +++ b/src/persistence/index.rs @@ -23,11 +23,13 @@ use std::collections::BTreeSet; use std::error::Error; use std::fmt::Debug; +use amplify::confinement; use rgb::{ Assign, AssignmentType, BundleId, ContractId, ExposedState, Extension, Genesis, GenesisSeal, GraphSeal, OpId, Operation, Opout, TransitionBundle, TypedAssigns, XChain, XOutputSeal, XWitnessId, }; +use strict_encoding::SerializeError; use crate::containers::{BundledWitness, Consignment, ToWitnessId}; use crate::persistence::StoreTransaction; @@ -83,6 +85,10 @@ impl From::Error>> f } } +impl From for IndexWriteError { + fn from(err: confinement::Error) -> Self { IndexWriteError::Connectivity(err.into()) } +} + #[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] #[display(doc_comments)] pub enum IndexInconsistency { diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 6f4d267e..7c689f5f 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -36,7 +36,7 @@ use rgb::{ Extension, Genesis, GenesisSeal, GraphSeal, Identity, OpId, Operation, Opout, Schema, SchemaId, SecretSeal, TransitionBundle, XChain, XOutputSeal, XWitnessId, }; -use strict_encoding::{StrictDeserialize, StrictSerialize}; +use strict_encoding::{SerializeError, StrictDeserialize, StrictSerialize}; use strict_types::TypeSystem; use super::{ @@ -49,6 +49,7 @@ use crate::containers::{ AnchorSet, ContentId, ContentRef, ContentSigs, SealWitness, SigBlob, Supplement, TrustLevel, }; use crate::interface::{Iface, IfaceClass, IfaceId, IfaceImpl, IfaceRef}; +use crate::persistence::fs::FsStored; use crate::resolvers::ResolveHeight; use crate::LIB_NAME_RGB_STD; @@ -87,7 +88,7 @@ impl StrictSerialize for MemStash {} impl StrictDeserialize for MemStash {} impl StoreTransaction for MemStash { - type TransactionErr = confinement::Error; + type TransactionErr = SerializeError; fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { self.dirty = true; @@ -95,7 +96,9 @@ impl StoreTransaction for MemStash { } fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { - // We do not do anything here since we do not actually save anything + if self.dirty { + self.store()?; + } Ok(()) } @@ -288,9 +291,9 @@ impl StashReadProvider for MemStash { } impl StashWriteProvider for MemStash { - type Error = confinement::Error; + type Error = SerializeError; - fn replace_schema(&mut self, schema: Schema) -> Result { + fn replace_schema(&mut self, schema: Schema) -> Result { let schema_id = schema.schema_id(); if !self.schemata.contains_key(&schema_id) { self.schemata.insert(schema_id, SchemaIfaces::new(schema))?; @@ -299,7 +302,7 @@ impl StashWriteProvider for MemStash { Ok(false) } - fn replace_iface(&mut self, iface: Iface) -> Result { + fn replace_iface(&mut self, iface: Iface) -> Result { let iface_id = iface.iface_id(); if !self.ifaces.contains_key(&iface_id) { self.ifaces.insert(iface_id, iface)?; @@ -308,7 +311,7 @@ impl StashWriteProvider for MemStash { Ok(false) } - fn replace_iimpl(&mut self, iimpl: IfaceImpl) -> Result { + fn replace_iimpl(&mut self, iimpl: IfaceImpl) -> Result { let schema_ifaces = self .schemata .get_mut(&iimpl.schema_id) @@ -329,7 +332,7 @@ impl StashWriteProvider for MemStash { Ok(()) } - fn add_supplement(&mut self, suppl: Supplement) -> Result<(), confinement::Error> { + fn add_supplement(&mut self, suppl: Supplement) -> Result<(), Self::Error> { match self.suppl.get_mut(&suppl.content_id) { None => { self.suppl.insert(suppl.content_id, confined_bset![suppl])?; @@ -339,25 +342,25 @@ impl StashWriteProvider for MemStash { Ok(()) } - fn replace_genesis(&mut self, genesis: Genesis) -> Result { + fn replace_genesis(&mut self, genesis: Genesis) -> Result { let contract_id = genesis.contract_id(); let present = self.geneses.insert(contract_id, genesis)?.is_some(); Ok(!present) } - fn replace_extension(&mut self, extension: Extension) -> Result { + fn replace_extension(&mut self, extension: Extension) -> Result { let opid = extension.id(); let present = self.extensions.insert(opid, extension)?.is_some(); Ok(!present) } - fn replace_bundle(&mut self, bundle: TransitionBundle) -> Result { + fn replace_bundle(&mut self, bundle: TransitionBundle) -> Result { let bundle_id = bundle.bundle_id(); let present = self.bundles.insert(bundle_id, bundle)?.is_some(); Ok(!present) } - fn replace_witness(&mut self, witness: SealWitness) -> Result { + fn replace_witness(&mut self, witness: SealWitness) -> Result { let witness_id = witness.witness_id(); let present = self.witnesses.insert(witness_id, witness)?.is_some(); Ok(!present) @@ -367,21 +370,21 @@ impl StashWriteProvider for MemStash { &mut self, id: AttachId, attach: MediumBlob, - ) -> Result { + ) -> Result { let present = self.attachments.insert(id, attach)?.is_some(); Ok(!present) } - fn consume_types(&mut self, types: TypeSystem) -> Result<(), confinement::Error> { - self.type_system.extend(types) + fn consume_types(&mut self, types: TypeSystem) -> Result<(), Self::Error> { + Ok(self.type_system.extend(types)?) } - fn replace_lib(&mut self, lib: Lib) -> Result { + fn replace_lib(&mut self, lib: Lib) -> Result { let present = self.libs.insert(lib.id(), lib)?.is_some(); Ok(!present) } - fn import_sigs(&mut self, content_id: ContentId, sigs: I) -> Result<(), confinement::Error> + fn import_sigs(&mut self, content_id: ContentId, sigs: I) -> Result<(), Self::Error> where I: IntoIterator { let sigs = sigs.into_iter().filter(|(id, _)| { match self.identities.get(id) { @@ -404,7 +407,7 @@ impl StashWriteProvider for MemStash { Ok(()) } - fn add_secret_seal(&mut self, seal: XChain) -> Result { + fn add_secret_seal(&mut self, seal: XChain) -> Result { let present = self.secret_seals.contains(&seal); self.secret_seals.push(seal)?; Ok(!present) @@ -437,7 +440,7 @@ impl StrictSerialize for MemState {} impl StrictDeserialize for MemState {} impl StoreTransaction for MemState { - type TransactionErr = confinement::Error; + type TransactionErr = SerializeError; fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { self.dirty = true; @@ -445,7 +448,9 @@ impl StoreTransaction for MemState { } fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { - // We do not do anything here since we do not actually save anything + if self.dirty { + self.store()?; + } Ok(()) } @@ -466,7 +471,7 @@ impl StateReadProvider for MemState { } impl StateWriteProvider for MemState { - type Error = confinement::Error; + type Error = SerializeError; fn create_or_update_state( &mut self, @@ -476,7 +481,9 @@ impl StateWriteProvider for MemState { let state = self.history.get(&contract_id); let updated = updater(state.cloned()).map_err(|e| StateUpdateError::Resolver(e.to_string()))?; - self.history.insert(contract_id, updated)?; + self.history + .insert(contract_id, updated) + .map_err(|e| StateUpdateError::Connectivity(e.into()))?; Ok(()) } @@ -541,7 +548,7 @@ impl StrictSerialize for MemIndex {} impl StrictDeserialize for MemIndex {} impl StoreTransaction for MemIndex { - type TransactionErr = confinement::Error; + type TransactionErr = SerializeError; fn begin_transaction(&mut self) -> Result<(), Self::TransactionErr> { self.dirty = true; @@ -549,7 +556,9 @@ impl StoreTransaction for MemIndex { } fn commit_transaction(&mut self) -> Result<(), Self::TransactionErr> { - // We do not do anything here since we do not actually save anything + if self.dirty { + self.store()?; + } Ok(()) } @@ -647,7 +656,7 @@ impl IndexReadProvider for MemIndex { } impl IndexWriteProvider for MemIndex { - type Error = confinement::Error; + type Error = SerializeError; fn register_contract(&mut self, contract_id: ContractId) -> Result { if !self.contract_index.contains_key(&contract_id) { From c2f83473eb612638b9b67c0fb5150c574c35171e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 10 Jul 2024 09:50:22 +0200 Subject: [PATCH 5/5] persistence: fix missed saving of the stash on secret seal creation --- src/persistence/stash.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 0382da53..bb27478a 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -592,9 +592,14 @@ impl Stash

{ &mut self, seal: XChain, ) -> Result> { - self.provider + self.begin_transaction()?; + let seal = self + .provider .add_secret_seal(seal) - .map_err(StashError::WriteProvider) + .inspect_err(|_| self.rollback_transaction()) + .map_err(StashError::WriteProvider)?; + self.commit_transaction()?; + Ok(seal) } }