ovr_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 clap::Parser;
19use driver::{btrfs, external, zfs};
20use ovr_ruc::{cmd, *};
21use std::{fmt, result::Result as StdResult, str::FromStr};
22
23/// Maximum number of snapshots that can be kept
24pub const CAP_MAX: u64 = 4096;
25
26/// `itv.pow(i)`, only useful in `SnapAlgo::Fade` alfo
27pub const STEP_CNT: usize = 10;
28
29/// The co-responding VAR-name of `--snapshot-volume`
30pub const ENV_VAR_BTM_VOLUME: &str = "BTM_VOLUME";
31
32/// Config structure of snapshot
33#[derive(Clone, Debug, Parser)]
34pub struct BtmCfg {
35    /// a global switch for enabling snapshot functions
36    #[clap(long)]
37    pub enable: bool,
38    /// interval between adjacent snapshots, default to 10 blocks
39    #[clap(short, long, default_value_t = 10)]
40    pub itv: u64,
41    /// the maximum number of snapshots that will be stored, default to 100
42    #[clap(short, long, default_value_t = 100)]
43    pub cap: u64,
44    /// Zfs or Btrfs or External, will try a guess if missing
45    #[clap(short, long, default_value_t = SnapMode::Zfs)]
46    pub mode: SnapMode,
47    /// Fair or Fade, default to 'Fair'
48    #[clap(short, long, default_value_t = SnapAlgo::Fair)]
49    pub algo: SnapAlgo,
50    /// a data volume containing both ledger data and tendermint data
51    #[clap(short = 'p', long, default_value_t = String::from("zfs/data"))]
52    pub volume: String,
53}
54
55impl Default for BtmCfg {
56    fn default() -> Self {
57        BtmCfg {
58            enable: false,
59            itv: 10,
60            cap: 100,
61            mode: SnapMode::Zfs,
62            algo: SnapAlgo::Fair,
63            volume: "zfs/data".to_owned(),
64        }
65    }
66}
67
68impl BtmCfg {
69    /// create a simple instance
70    #[inline(always)]
71    pub fn new() -> Self {
72        Self::new_enabled()
73    }
74
75    #[inline(always)]
76    fn new_enabled() -> Self {
77        BtmCfg {
78            enable: true,
79            ..Self::default()
80        }
81    }
82
83    /// Used in client side
84    #[inline(always)]
85    pub fn new_client_hdr() -> Self {
86        Self::new_enabled()
87    }
88
89    /// generate a snapshot for the latest state of blockchain
90    #[inline(always)]
91    pub fn snapshot(&self, idx: u64) -> Result<()> {
92        alt!(!self.enable, return Ok(()));
93
94        // sync data to disk before snapshoting
95        nix::unistd::sync();
96
97        match self.mode {
98            SnapMode::Zfs => zfs::gen_snapshot(self, idx).c(d!()),
99            SnapMode::Btrfs => btrfs::gen_snapshot(self, idx).c(d!()),
100            SnapMode::External => external::gen_snapshot(self, idx).c(d!()),
101        }
102    }
103
104    /// rollback the state of blockchain to a specificed height
105    #[inline(always)]
106    pub fn rollback(&self, idx: Option<u64>, strict: bool) -> Result<()> {
107        match self.mode {
108            SnapMode::Zfs => zfs::rollback(self, idx, strict).c(d!()),
109            SnapMode::Btrfs => btrfs::rollback(self, idx, strict).c(d!()),
110            SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
111        }
112    }
113
114    /// Get snapshot list in 'DESC' order.
115    #[inline(always)]
116    pub fn get_sorted_snapshots(&self) -> Result<Vec<u64>> {
117        match self.mode {
118            SnapMode::Zfs => zfs::sorted_snapshots(self).c(d!()),
119            SnapMode::Btrfs => btrfs::sorted_snapshots(self).c(d!()),
120            SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
121        }
122    }
123
124    /// try to guess a correct mode
125    /// NOTE: not suitable for `External` mode
126    #[inline(always)]
127    pub fn guess_mode(volume: &str) -> Result<SnapMode> {
128        zfs::check(volume)
129            .c(d!())
130            .map(|_| SnapMode::Zfs)
131            .or_else(|e| btrfs::check(volume).c(d!(e)).map(|_| SnapMode::Btrfs))
132    }
133
134    #[inline(always)]
135    fn get_cap(&self) -> u64 {
136        alt!(self.cap > CAP_MAX, CAP_MAX, self.cap)
137    }
138
139    /// List all existing snapshots.
140    pub fn list_snapshots(&self) -> Result<()> {
141        println!("Available snapshots are listed below:");
142        self.get_sorted_snapshots().c(d!()).map(|list| {
143            list.into_iter().rev().for_each(|h| {
144                println!("    {}", h);
145            })
146        })
147    }
148
149    /// Clean all existing snapshots.
150    pub fn clean_snapshots(&self) -> Result<()> {
151        self.get_sorted_snapshots().c(d!()).map(|list| {
152            list.into_iter().rev().for_each(|height| {
153                let cmd = match self.mode {
154                    SnapMode::Btrfs => {
155                        format!("btrfs subvolume delete {}@{}", &self.volume, height)
156                    }
157                    SnapMode::Zfs => format!("zfs destroy {}@{}", &self.volume, height),
158                    _ => pnk!(Err(eg!("Unsupported deriver"))),
159                };
160                info_omit!(cmd::exec_output(&cmd));
161            });
162        })
163    }
164}
165
166/// # Inner Operations
167///
168/// assume:
169/// - root volume of zfs is `zfs`
170/// - root volume of btrfs is `/btrfs`
171/// - business data is stored in `<root volume>/data`
172/// - target block height to recover is 123456
173///
174/// ## snapshot
175///
176/// ```shell
177/// # zfs filesystem
178/// zfs destroy zfs/data@123456 2>/dev/null
179/// zfs snapshot zfs/data@123456
180///
181/// # btrfs filesystem
182/// rm -rf /btrfs/data@123456 2>/dev/null
183/// btrfs subvolume snapshot /btrfs/data /btrfs/data@123456
184/// ```
185///
186/// ## rollback
187///
188/// ```shell
189/// # zfs filesystem
190/// zfs rollback -r zfs/data@123456
191///
192/// # btrfs filesystem
193/// rm -rf /btrfs/data || exit 1
194/// btrfs subvolume snapshot /btrfs/data@123456 /btrfs/data
195/// ```
196#[derive(Clone, Copy, Debug)]
197pub enum SnapMode {
198    /// available on some Linux distributions and FreeBSD
199    /// - Ubuntu Linux
200    /// - Gentoo Linux
201    /// - FreeBSD
202    /// - ...
203    Zfs,
204    /// available on most Linux distributions,
205    /// but its user experience is worse than zfs
206    Btrfs,
207    /// TODO: unimplemented!
208    /// rely on an external independent process
209    External,
210}
211
212impl SnapMode {
213    #[inline(always)]
214    #[allow(missing_docs)]
215    pub fn from_string(m: &str) -> Result<Self> {
216        match m.to_lowercase().as_str() {
217            "zfs" => Ok(Self::Zfs),
218            "btrfs" => Ok(Self::Btrfs),
219            "external" => Ok(Self::External),
220            _ => Err(eg!()),
221        }
222    }
223}
224
225impl Default for SnapMode {
226    fn default() -> Self {
227        Self::Zfs
228    }
229}
230
231impl fmt::Display for SnapMode {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        let contents = match self {
234            Self::Zfs => "Zfs",
235            Self::Btrfs => "Btrfs",
236            Self::External => "External",
237        };
238        write!(f, "{}", contents)
239    }
240}
241
242impl FromStr for SnapMode {
243    type Err = String;
244    fn from_str(s: &str) -> StdResult<Self, Self::Err> {
245        Self::from_string(s).c(d!()).map_err(|e| e.to_string())
246    }
247}
248
249/// Snapshot management algorithm
250#[derive(Clone, Copy, Debug)]
251pub enum SnapAlgo {
252    /// snapshots are saved at fixed intervals
253    Fair,
254    /// snapshots are saved in decreasing density
255    Fade,
256}
257
258impl SnapAlgo {
259    #[inline(always)]
260    #[allow(missing_docs)]
261    pub fn from_string(m: &str) -> Result<Self> {
262        match m.to_lowercase().as_str() {
263            "fair" => Ok(Self::Fair),
264            "fade" => Ok(Self::Fade),
265            _ => Err(eg!()),
266        }
267    }
268}
269
270impl Default for SnapAlgo {
271    fn default() -> Self {
272        Self::Fair
273    }
274}
275
276impl fmt::Display for SnapAlgo {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        let contents = match self {
279            Self::Fair => "Fair",
280            Self::Fade => "Fade",
281        };
282        write!(f, "{}", contents)
283    }
284}
285
286impl FromStr for SnapAlgo {
287    type Err = String;
288    fn from_str(s: &str) -> StdResult<Self, Self::Err> {
289        Self::from_string(s).c(d!()).map_err(|e| e.to_string())
290    }
291}