Skip to main content

urma_node/daemon/
inputs.rs

1use crate::{Error, model::ArchiveConfig, platform::read_admin_file};
2use libp2p::identity::{KeyType, Keypair};
3use std::{fmt::Display, io, path::Path};
4use urma_chain::observation::Chain;
5use urma_runtime::p2p::{HeaderBootstrap, context_headers};
6
7struct Limits;
8
9impl Limits {
10    const CONFIG: usize = 64 * 1024;
11    const BOOTSTRAP: usize = 1024 * 1024;
12    const TOKEN: usize = 128;
13    const PEER_KEY: usize = 4096;
14}
15
16pub(super) fn invalid(error: impl Display) -> Error {
17    Error::Invalid(error.to_string())
18}
19
20#[derive(serde::Deserialize)]
21#[serde(deny_unknown_fields)]
22struct HeaderBootstrapFile {
23    version: u32,
24    chain: Chain,
25    height: u64,
26    header: String,
27    ancestors: Vec<String>,
28}
29
30pub fn read_archive_config(path: &Path) -> Result<ArchiveConfig, Error> {
31    Ok(serde_json::from_slice(&read_admin_file(
32        path,
33        Limits::CONFIG,
34    )?)?)
35}
36
37pub fn read_token(path: &Path) -> Result<String, Error> {
38    let bytes = match urma_io::read_shared(path, Limits::TOKEN) {
39        Ok(bytes) => bytes,
40        Err(urma_io::Error::PrivatePermissions) => {
41            return Err(Error::Invalid(
42                "service token must not be writable by group or open to others".into(),
43            ));
44        }
45        Err(error) => return Err(invalid(format!("invalid service token file: {error}"))),
46    };
47    Ok(std::str::from_utf8(&bytes)
48        .map_err(invalid)?
49        .trim()
50        .to_string())
51}
52
53pub fn read_peer_key(path: &Path) -> Result<Keypair, Error> {
54    if !std::fs::symlink_metadata(path)?.is_file() {
55        return Err(Error::Invalid(
56            "peer key must be a regular private file".into(),
57        ));
58    }
59    let bytes = urma_io::read_private(path, Limits::PEER_KEY).map_err(invalid)?;
60    let key = Keypair::from_protobuf_encoding(&bytes).map_err(invalid)?;
61    if key.key_type() != KeyType::Ed25519 {
62        return Err(Error::Invalid("peer identity must be Ed25519".into()));
63    }
64    Ok(key)
65}
66
67pub fn read_header_bootstrap(
68    path: &Path,
69    chain: Chain,
70    application_height: u64,
71) -> Result<HeaderBootstrap, Error> {
72    let bytes = match urma_io::read_bounded(path, Limits::BOOTSTRAP) {
73        Ok(bytes) => bytes,
74        Err(urma_io::Error::TooLarge { .. }) => {
75            return Err(Error::Invalid("header bootstrap exceeds 1 MiB".into()));
76        }
77        Err(error) => return Err(io::Error::other(error).into()),
78    };
79    let input: HeaderBootstrapFile = serde_json::from_slice(&bytes).map_err(invalid)?;
80    if input.version != 1
81        || input.chain.genesis().map_err(invalid)? != chain.genesis().map_err(invalid)?
82    {
83        return Err(Error::Invalid(
84            "header bootstrap version or chain mismatch".into(),
85        ));
86    }
87    if input.height > application_height {
88        return Err(Error::Invalid(
89            "application start precedes the supplied header anchor".into(),
90        ));
91    }
92    let context = u64::try_from(context_headers(chain)).map_err(invalid)?;
93    let count = usize::try_from(input.height.min(context)).map_err(invalid)?;
94    if input.ancestors.len() != count {
95        return Err(Error::Invalid(format!(
96            "header bootstrap needs exactly {count} ancestors"
97        )));
98    }
99    let anchor = header(&input.header)?;
100    let ancestors = input
101        .ancestors
102        .iter()
103        .map(|raw| header(raw))
104        .collect::<Result<Vec<_>, _>>()?;
105    HeaderBootstrap::new(chain, input.height, anchor, ancestors).map_err(invalid)
106}
107
108fn header(raw: &str) -> Result<bitcoin::block::Header, Error> {
109    if raw.len() != 160 {
110        return Err(Error::Invalid(
111            "bootstrap header must be exactly 80 bytes encoded as hex".into(),
112        ));
113    }
114    bitcoin::consensus::deserialize(&hex::decode(raw).map_err(invalid)?).map_err(invalid)
115}
116
117pub(super) fn private_directory(path: &Path) -> Result<(), Error> {
118    match urma_io::create_private_directory(path) {
119        Ok(()) => Ok(()),
120        Err(error)
121            if error.kind() == io::ErrorKind::AlreadyExists
122                && std::fs::symlink_metadata(path)?.is_dir() =>
123        {
124            Ok(())
125        }
126        Err(error) => Err(error.into()),
127    }
128}