basecoin_store/impls/
revertible.rs

1use ics23::CommitmentProof;
2use tracing::trace;
3
4use crate::context::{ProvableStore, Store};
5use crate::types::{Height, Path};
6
7/// A wrapper store that implements rudimentary `apply()`/`reset()` support for other stores.
8///
9/// [`RevertibleStore`] relies on maintaining a list of processed operations - `delete` and `set`.
10///
11/// If it has to revert a failed transaction, it _reverts_ the previous operations as so:
12/// - If reverting an overwriting `set` or `delete` operation, it performs a `set` with the old value.
13/// - If reverting a non-overwriting `set`, it `delete`s the current value.
14///
15/// Note that this scheme makes it trickier to maintain deterministic Merkle root hashes:
16/// an overwriting `set` doesn't reorganize a Merkle tree - but non-overwriting `set` and `delete`
17/// operations may reorganize a Merkle tree - which may change the root hash. However, a Merkle
18/// store should have no effect on a failed transaction.
19#[deprecated(
20    since = "TBD",
21    note = "RevertibleStore has a bug where using the operation log to revert changes does not guarantee deterministic Merkle root hashes. Use InMemoryStore which implements a correct rollback procedure."
22)]
23#[derive(Clone, Debug)]
24pub struct RevertibleStore<S> {
25    /// backing store
26    store: S,
27    /// operation log for recording rollback operations in preserved order
28    op_log: Vec<RevertOp>,
29}
30
31#[derive(Clone, Debug)]
32enum RevertOp {
33    Delete(Path),
34    Set(Path, Vec<u8>),
35}
36
37impl<S> RevertibleStore<S>
38where
39    S: Store,
40{
41    pub fn new(store: S) -> Self {
42        Self {
43            store,
44            op_log: vec![],
45        }
46    }
47}
48
49impl<S> Default for RevertibleStore<S>
50where
51    S: Default + Store,
52{
53    fn default() -> Self {
54        Self::new(S::default())
55    }
56}
57
58impl<S> Store for RevertibleStore<S>
59where
60    S: Store,
61{
62    type Error = S::Error;
63
64    #[inline]
65    fn set(&mut self, path: Path, value: Vec<u8>) -> Result<Option<Vec<u8>>, Self::Error> {
66        let old_value = self.store.set(path.clone(), value)?;
67        match old_value {
68            // None implies this was an insert op, so we record the revert op as delete op
69            None => self.op_log.push(RevertOp::Delete(path)),
70            // Some old value implies this was an update op, so we record the revert op as a set op
71            // with the old value
72            Some(ref old_value) => self.op_log.push(RevertOp::Set(path, old_value.clone())),
73        }
74        Ok(old_value)
75    }
76
77    #[inline]
78    fn get(&self, height: Height, path: &Path) -> Option<Vec<u8>> {
79        self.store.get(height, path)
80    }
81
82    #[inline]
83    fn delete(&mut self, path: &Path) {
84        self.store.delete(path)
85    }
86
87    #[inline]
88    fn commit(&mut self) -> Result<Vec<u8>, Self::Error> {
89        // call `apply()` before `commit()` to make sure all operations are applied
90        self.apply()?;
91        self.store.commit()
92    }
93
94    #[inline]
95    fn apply(&mut self) -> Result<(), Self::Error> {
96        // note that we do NOT call the backing store's apply here - this allows users to create
97        // multilayered `WalStore`s
98        self.op_log.clear();
99        Ok(())
100    }
101
102    /// Revert all operations in the operation log.
103    ///
104    /// This method doesn't guarantee that the Merkle tree will be reverted to the correct previous root hash.
105    /// It should be avoided. Use `InMemoryStore` directly which implements a correct rollback procedure.
106    ///
107    /// GH issue: informalsystems/basecoin-rs#129
108    #[inline]
109    fn reset(&mut self) {
110        // note that we do NOT call the backing store's reset here - this allows users to create
111        // multilayered `WalStore`s
112        trace!("Rollback operation log changes");
113        while let Some(op) = self.op_log.pop() {
114            match op {
115                RevertOp::Delete(path) => self.delete(&path),
116                RevertOp::Set(path, value) => {
117                    // FIXME: potential non-termination
118                    // self.set() may insert a new op into the op_log
119                    self.set(path, value).unwrap(); // safety - reset failures are unrecoverable
120                }
121            }
122        }
123    }
124
125    #[inline]
126    fn current_height(&self) -> u64 {
127        self.store.current_height()
128    }
129
130    #[inline]
131    fn get_keys(&self, key_prefix: &Path) -> Vec<Path> {
132        self.store.get_keys(key_prefix)
133    }
134}
135
136impl<S> ProvableStore for RevertibleStore<S>
137where
138    S: ProvableStore,
139{
140    #[inline]
141    fn root_hash(&self) -> Vec<u8> {
142        self.store.root_hash()
143    }
144
145    #[inline]
146    fn get_proof(&self, height: Height, key: &Path) -> Option<CommitmentProof> {
147        self.store.get_proof(height, key)
148    }
149}