soroban_ledger_snapshot/
lib.rs

1use serde_with::serde_as;
2use std::{
3    fs::{create_dir_all, File},
4    io::{self, Read, Write},
5    path::Path,
6    rc::Rc,
7};
8
9use soroban_env_host::{
10    storage::SnapshotSource,
11    xdr::{LedgerEntry, LedgerKey},
12    HostError, LedgerInfo,
13};
14
15#[derive(thiserror::Error, Debug)]
16pub enum Error {
17    #[error("io")]
18    Io(#[from] io::Error),
19    #[error("serde")]
20    Serde(#[from] serde_json::Error),
21}
22
23/// Ledger snapshot stores a snapshot of a ledger that can be restored for use
24/// in environments as a [`LedgerInfo`] and a [`SnapshotSource`].
25#[serde_as]
26#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub struct LedgerSnapshot {
29    pub protocol_version: u32,
30    pub sequence_number: u32,
31    pub timestamp: u64,
32    #[serde_as(as = "serde_with::hex::Hex")]
33    pub network_id: [u8; 32],
34    pub base_reserve: u32,
35    pub min_persistent_entry_ttl: u32,
36    pub min_temp_entry_ttl: u32,
37    pub max_entry_ttl: u32,
38    pub ledger_entries: Vec<(Box<LedgerKey>, (Box<LedgerEntry>, Option<u32>))>,
39}
40
41impl LedgerSnapshot {
42    // Create a ledger snapshot from ledger info and a set of entries.
43    pub fn from<'a>(
44        info: LedgerInfo,
45        entries: impl IntoIterator<Item = (&'a Box<LedgerKey>, (&'a Box<LedgerEntry>, Option<u32>))>,
46    ) -> Self {
47        let mut s = Self::default();
48        s.set_ledger_info(info);
49        s.set_entries(entries);
50        s
51    }
52
53    /// Update the snapshot with the state within the given [`soroban_env_host::Host`].
54    ///
55    /// The ledger info of the host will overwrite the ledger info in the
56    /// snapshot.  The entries in the host's storage will overwrite entries in
57    /// the snapshot. Existing entries in the snapshot that are untouched by the
58    /// host will remain.
59    #[cfg(feature = "testutils")]
60    pub fn update(&mut self, host: &soroban_env_host::Host) {
61        let _result = host.with_ledger_info(|li| {
62            self.set_ledger_info(li.clone());
63            Ok(())
64        });
65        self.update_entries(&host.get_stored_entries().unwrap());
66    }
67
68    // Get the ledger info in the snapshot.
69    pub fn ledger_info(&self) -> LedgerInfo {
70        LedgerInfo {
71            protocol_version: self.protocol_version,
72            sequence_number: self.sequence_number,
73            timestamp: self.timestamp,
74            network_id: self.network_id,
75            base_reserve: self.base_reserve,
76            min_persistent_entry_ttl: self.min_persistent_entry_ttl,
77            min_temp_entry_ttl: self.min_temp_entry_ttl,
78            max_entry_ttl: self.max_entry_ttl,
79        }
80    }
81
82    /// Set the ledger info in the snapshot.
83    pub fn set_ledger_info(&mut self, info: LedgerInfo) {
84        self.protocol_version = info.protocol_version;
85        self.sequence_number = info.sequence_number;
86        self.timestamp = info.timestamp;
87        self.network_id = info.network_id;
88        self.base_reserve = info.base_reserve;
89        self.min_persistent_entry_ttl = info.min_persistent_entry_ttl;
90        self.min_temp_entry_ttl = info.min_temp_entry_ttl;
91        self.max_entry_ttl = info.max_entry_ttl;
92    }
93
94    /// Get the entries in the snapshot.
95    pub fn entries(
96        &self,
97    ) -> impl IntoIterator<Item = (&Box<LedgerKey>, &(Box<LedgerEntry>, Option<u32>))> {
98        self.ledger_entries.iter().map(|(k, v)| (k, v))
99    }
100
101    /// Replace the entries in the snapshot with the entries in the iterator.
102    pub fn set_entries<'a>(
103        &mut self,
104        entries: impl IntoIterator<Item = (&'a Box<LedgerKey>, (&'a Box<LedgerEntry>, Option<u32>))>,
105    ) {
106        self.ledger_entries.clear();
107        for (k, e) in entries {
108            self.ledger_entries.push((k.clone(), (e.0.clone(), e.1)));
109        }
110    }
111
112    /// Update entries in the snapshot by adding or replacing any entries that
113    /// have entry in the input iterator, or removing any that does not have an
114    /// entry.
115    pub fn update_entries<'a>(
116        &mut self,
117        entries: impl IntoIterator<Item = &'a (Rc<LedgerKey>, Option<(Rc<LedgerEntry>, Option<u32>)>)>,
118    ) {
119        for (k, e) in entries {
120            let i = self.ledger_entries.iter().position(|(ik, _)| **ik == **k);
121            if let Some((entry, live_until_ledger)) = e {
122                let new = (
123                    Box::new((**k).clone()),
124                    (Box::new((**entry).clone()), *live_until_ledger),
125                );
126                if let Some(i) = i {
127                    self.ledger_entries[i] = new;
128                } else {
129                    self.ledger_entries.push(new);
130                }
131            } else if let Some(i) = i {
132                self.ledger_entries.swap_remove(i);
133            }
134        }
135    }
136}
137
138impl LedgerSnapshot {
139    // Read in a [`LedgerSnapshot`] from a reader.
140    pub fn read(r: impl Read) -> Result<LedgerSnapshot, Error> {
141        Ok(serde_json::from_reader::<_, LedgerSnapshot>(r)?)
142    }
143
144    // Read in a [`LedgerSnapshot`] from a file.
145    pub fn read_file(p: impl AsRef<Path>) -> Result<LedgerSnapshot, Error> {
146        Self::read(File::open(p)?)
147    }
148
149    // Write a [`LedgerSnapshot`] to a writer.
150    pub fn write(&self, w: impl Write) -> Result<(), Error> {
151        Ok(serde_json::to_writer_pretty(w, self)?)
152    }
153
154    // Write a [`LedgerSnapshot`] to file.
155    pub fn write_file(&self, p: impl AsRef<Path>) -> Result<(), Error> {
156        let p = p.as_ref();
157        if let Some(dir) = p.parent() {
158            if !dir.exists() {
159                create_dir_all(dir)?;
160            }
161        }
162        self.write(File::create(p)?)
163    }
164}
165
166impl Default for LedgerSnapshot {
167    fn default() -> Self {
168        Self {
169            protocol_version: 23,
170            sequence_number: Default::default(),
171            timestamp: Default::default(),
172            network_id: Default::default(),
173            base_reserve: Default::default(),
174            ledger_entries: Vec::default(),
175            min_persistent_entry_ttl: Default::default(),
176            min_temp_entry_ttl: Default::default(),
177            max_entry_ttl: Default::default(),
178        }
179    }
180}
181
182impl SnapshotSource for &LedgerSnapshot {
183    fn get(
184        &self,
185        key: &Rc<LedgerKey>,
186    ) -> Result<Option<(Rc<LedgerEntry>, Option<u32>)>, HostError> {
187        match self.ledger_entries.iter().find(|(k, _)| **k == **key) {
188            Some((_, v)) => Ok(Some((Rc::new(*v.0.clone()), v.1))),
189            None => Ok(None),
190        }
191    }
192}
193
194impl SnapshotSource for LedgerSnapshot {
195    fn get(
196        &self,
197        key: &Rc<LedgerKey>,
198    ) -> Result<Option<(Rc<LedgerEntry>, Option<u32>)>, HostError> {
199        <_ as SnapshotSource>::get(&self, key)
200    }
201}