leo_lang/cli/commands/query/
block.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18
19use crate::cli::context::Context;
20use clap::Parser;
21
22// Query on-chain information related to blocks.
23#[derive(Parser, Debug)]
24pub struct LeoBlock {
25    #[clap(help = "Fetch a block by specifying its height or hash", required_unless_present_any = &["latest", "latest_hash", "latest_height", "range"])]
26    pub(crate) id: Option<String>,
27    #[arg(short, long, help = "Get the latest block", default_value = "false", conflicts_with_all(["latest_hash", "latest_height", "range", "transactions", "to_height"]))]
28    pub(crate) latest: bool,
29    #[arg(long, help = "Get the latest block hash", default_value = "false", conflicts_with_all(["latest", "latest_height", "range", "transactions", "to_height"]))]
30    pub(crate) latest_hash: bool,
31    #[arg(long, help = "Get the latest block height", default_value = "false", conflicts_with_all(["latest", "latest_hash", "range", "transactions", "to_height"]))]
32    pub(crate) latest_height: bool,
33    #[arg(short, long, help = "Get up to 50 consecutive blocks", number_of_values = 2, value_names = &["START_HEIGHT", "END_HEIGHT"], conflicts_with_all(["latest", "latest_hash", "latest_height", "transactions", "to_height"]))]
34    pub(crate) range: Option<Vec<String>>,
35    #[arg(
36        short,
37        long,
38        help = "Get all transactions at the specified block height",
39        conflicts_with("to_height"),
40        default_value = "false"
41    )]
42    pub(crate) transactions: bool,
43    #[arg(long, help = "Lookup the block height corresponding to a hash value", default_value = "false")]
44    pub(crate) to_height: bool,
45}
46
47impl Command for LeoBlock {
48    type Input = ();
49    type Output = String;
50
51    fn log_span(&self) -> Span {
52        tracing::span!(tracing::Level::INFO, "Leo")
53    }
54
55    fn prelude(&self, _context: Context) -> Result<Self::Input> {
56        Ok(())
57    }
58
59    fn apply(self, _context: Context, _input: Self::Input) -> Result<Self::Output> {
60        // Build custom url to fetch from based on the flags and user's input.
61        let url = if self.latest_height {
62            "block/height/latest".to_string()
63        } else if self.latest_hash {
64            "block/hash/latest".to_string()
65        } else if self.latest {
66            "block/latest".to_string()
67        } else if let Some(range) = self.range {
68            // Make sure the range is composed of valid numbers.
69            is_valid_numerical_input(&range[0])?;
70            is_valid_numerical_input(&range[1])?;
71
72            // Parse the range values.
73            let end = &range[1].parse::<u32>().map_err(|_| UtilError::invalid_bound(&range[1]))?;
74            let start = &range[0].parse::<u32>().map_err(|_| UtilError::invalid_bound(&range[0]))?;
75            // Make sure the range is not too large.
76            if end - start > 50 {
77                return Err(UtilError::invalid_range().into());
78            }
79            format!("blocks?start={}&end={}", range[0], range[1])
80        } else if self.transactions {
81            is_valid_numerical_input(&self.id.clone().unwrap())?;
82            format!("block/{}/transactions", self.id.unwrap()).to_string()
83        } else if self.to_height {
84            let id = self.id.unwrap();
85            is_valid_hash(&id)?;
86            format!("height/{}", id).to_string()
87        } else if let Some(id) = self.id {
88            is_valid_height_or_hash(&id)?;
89            format!("block/{}", id)
90        } else {
91            unreachable!("All cases are covered")
92        };
93
94        Ok(url)
95    }
96}