btm/
lib.rs

1//!
2//! # A Recover Mechanism for Blockchain Scene
3//!
4//! automatic operations:
5//! - create a light-weight(COW) snapshot for each block
6//! - clean up expired snapshots
7//!
8
9#![cfg(target_os = "linux")]
10#![deny(warnings)]
11#![deny(missing_docs)]
12
13mod api;
14mod driver;
15
16pub use api::server::run_daemon;
17
18use driver::{btrfs, external, zfs};
19use ruc::{cmd, *};
20use std::{fmt, result::Result as StdResult, str::FromStr};
21
22/// Maximum number of snapshots that can be kept
23pub const CAP_MAX: u64 = 4096;
24
25/// `itv.pow(i)`,
26/// only useful within the `SnapAlgo::Fade` algo
27pub const STEP_CNT: usize = 10;
28
29/// Configures of snapshot mgmt
30#[derive(Clone, Debug)]
31pub struct BtmCfg {
32    /// The interval between adjacent snapshots, default to 10 blocks
33    pub itv: u64,
34    /// The maximum number of snapshots that will be stored, default to 100
35    pub cap: u64,
36    /// How many snapshots should be kept after a `clean_snapshots`, default to 0
37    pub cap_clean_kept: usize,
38    /// Zfs or Btrfs or External, should try a guess if missing
39    pub mode: SnapMode,
40    /// Fair or Fade, default to 'Fair'
41    pub algo: SnapAlgo,
42    /// A data volume containing all blockchain data
43    pub volume: String,
44}
45
46impl BtmCfg {
47    // Check mistakes
48    fn check(&self) -> Result<()> {
49        self.itv.checked_pow(STEP_CNT as u32).c(d!()).map(|_| ())
50    }
51
52    /// Create a simple instance
53    #[inline(always)]
54    pub fn new(volume: &str, mode: Option<&str>) -> Result<Self> {
55        let mode = if let Some(m) = mode {
56            SnapMode::from_str(m).map_err(|e| eg!(e))?
57        } else {
58            SnapMode::guess(volume).c(d!())?
59        };
60        Ok(Self {
61            itv: 10,
62            cap: 100,
63            cap_clean_kept: 0,
64            mode,
65            algo: SnapAlgo::Fair,
66            volume: volume.to_owned(),
67        })
68    }
69
70    /// Generate a snapshot for the latest state of blockchain
71    #[inline(always)]
72    pub fn snapshot(&self, idx: u64) -> Result<()> {
73        // sync data to disk before snapshoting
74        nix::unistd::sync();
75
76        match self.mode {
77            SnapMode::Zfs => zfs::gen_snapshot(self, idx).c(d!()),
78            SnapMode::Btrfs => btrfs::gen_snapshot(self, idx).c(d!()),
79            SnapMode::External => external::gen_snapshot(self, idx).c(d!()),
80        }
81    }
82
83    /// Rollback the state of blockchain to a specificed height
84    #[inline(always)]
85    pub fn rollback(&self, idx: Option<i128>, strict: bool) -> Result<()> {
86        match self.mode {
87            SnapMode::Zfs => zfs::rollback(self, idx, strict).c(d!()),
88            SnapMode::Btrfs => btrfs::rollback(self, idx, strict).c(d!()),
89            SnapMode::External => Err(eg!("please use the `btm` tool in `External` mode")),
90        }
91    }
92
93    /// Get snapshot list in 'DESC' order.
94    #[inline(always)]
95    pub fn get_sorted_snapshots(&self) -> Result<Vec<u64>> {
96        match self.mode {
97            SnapMode::Zfs => zfs::sorted_snapshots(self).c(d!()),
98            SnapMode::Btrfs => btrfs::sorted_snapshots(self).c(d!()),
99            SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
100        }
101    }
102
103    #[inline(always)]
104    fn get_cap(&self) -> u64 {
105        alt!(self.cap > CAP_MAX, CAP_MAX, self.cap)
106    }
107
108    /// List all existing snapshots.
109    pub fn list_snapshots(&self) -> Result<()> {
110        println!("Available snapshots are listed below:");
111        self.get_sorted_snapshots().c(d!()).map(|list| {
112            list.into_iter().rev().for_each(|h| {
113                println!("    {}", h);
114            })
115        })
116    }
117
118    /// Clean all existing snapshots.
119    pub fn clean_snapshots(&self) -> Result<()> {
120        self.get_sorted_snapshots().c(d!()).map(|list| {
121            list.into_iter()
122                .skip(self.cap_clean_kept)
123                .rev()
124                .for_each(|height| {
125                    let cmd = match self.mode {
126                        SnapMode::Btrfs => {
127                            format!("btrfs subvolume delete {}@{}", &self.volume, height)
128                        }
129                        SnapMode::Zfs => format!("zfs destroy {}@{}", &self.volume, height),
130                        _ => pnk!(Err(eg!("Unsupported deriver"))),
131                    };
132                    info_omit!(cmd::exec_output(&cmd));
133                });
134        })
135    }
136}
137
138/// # Inner Operations
139///
140/// assume:
141/// - root volume of zfs is `zfs`
142/// - root volume of btrfs is `/btrfs`
143/// - business data is stored in `<root volume>/data`
144/// - target block height to recover is 123456
145///
146/// ## snapshot
147///
148/// ```shell
149/// # zfs filesystem
150/// zfs destroy zfs/data@123456 2>/dev/null
151/// zfs snapshot zfs/data@123456
152///
153/// # btrfs filesystem
154/// rm -rf /btrfs/data@123456 2>/dev/null
155/// btrfs subvolume snapshot /btrfs/data /btrfs/data@123456
156/// ```
157///
158/// ## rollback
159///
160/// ```shell
161/// # zfs filesystem
162/// zfs rollback -r zfs/data@123456
163///
164/// # btrfs filesystem
165/// rm -rf /btrfs/data || exit 1
166/// btrfs subvolume snapshot /btrfs/data@123456 /btrfs/data
167/// ```
168#[derive(Clone, Copy, Debug)]
169pub enum SnapMode {
170    /// Available on some Linux distributions and FreeBSD
171    /// - Ubuntu Linux
172    /// - Gentoo Linux
173    /// - FreeBSD
174    /// - ...
175    Zfs,
176    /// Available on most Linux distributions,
177    /// but its user experience is worse than zfs
178    Btrfs,
179    /// Rely on an external independent process
180    External,
181}
182
183impl SnapMode {
184    #[inline(always)]
185    #[allow(missing_docs)]
186    pub fn from_string(m: &str) -> Result<Self> {
187        match m.to_lowercase().as_str() {
188            "zfs" => Ok(Self::Zfs),
189            "btrfs" => Ok(Self::Btrfs),
190            "external" => Ok(Self::External),
191            _ => Err(eg!()),
192        }
193    }
194
195    /// Try to determine which mode can be used on the target volume
196    ///
197    /// NOTE:
198    /// not suitable for the `External` mode.
199    pub fn guess(volume: &str) -> Result<Self> {
200        zfs::check(volume)
201            .c(d!())
202            .map(|_| SnapMode::Zfs)
203            .or_else(|e| btrfs::check(volume).c(d!(e)).map(|_| SnapMode::Btrfs))
204    }
205}
206
207impl Default for SnapMode {
208    fn default() -> Self {
209        Self::External
210    }
211}
212
213impl fmt::Display for SnapMode {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        let contents = match self {
216            Self::Zfs => "Zfs",
217            Self::Btrfs => "Btrfs",
218            Self::External => "External",
219        };
220        write!(f, "{}", contents)
221    }
222}
223
224impl FromStr for SnapMode {
225    type Err = String;
226    fn from_str(s: &str) -> StdResult<Self, Self::Err> {
227        Self::from_string(s).c(d!()).map_err(|e| e.to_string())
228    }
229}
230
231/// Snapshot management algorithm
232#[derive(Clone, Copy, Debug)]
233pub enum SnapAlgo {
234    /// snapshots are saved at fixed intervals
235    Fair,
236    /// snapshots are saved in decreasing density
237    Fade,
238}
239
240impl SnapAlgo {
241    #[inline(always)]
242    #[allow(missing_docs)]
243    pub fn from_string(m: &str) -> Result<Self> {
244        match m.to_lowercase().as_str() {
245            "fair" => Ok(Self::Fair),
246            "fade" => Ok(Self::Fade),
247            _ => Err(eg!()),
248        }
249    }
250}
251
252impl Default for SnapAlgo {
253    fn default() -> Self {
254        Self::Fair
255    }
256}
257
258impl fmt::Display for SnapAlgo {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        let contents = match self {
261            Self::Fair => "Fair",
262            Self::Fade => "Fade",
263        };
264        write!(f, "{}", contents)
265    }
266}
267
268impl FromStr for SnapAlgo {
269    type Err = String;
270    fn from_str(s: &str) -> StdResult<Self, Self::Err> {
271        Self::from_string(s).c(d!()).map_err(|e| e.to_string())
272    }
273}