Skip to main content

urma_runtime/
plan.rs

1use crate::error::{Error, ensure};
2use crate::publication::PublicPlan;
3use crate::storage;
4use crate::{node::Node, planner::Planner, validation};
5use serde::{Deserialize, Serialize};
6use sha2::{Digest, Sha256};
7use std::path::Path;
8use urma_chain::observation::Chain;
9use urma_core::{
10    format::PublicRecord,
11    multipart::{DataPart, Geometry, LeafManifest, MultipartRecord, RootManifest},
12};
13use urma_identity::identity::IdentitySigner;
14
15#[derive(Clone, Copy, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17pub struct PlanLimits {
18    pub fee_rate: u64,
19    pub max_fee: u64,
20    pub max_records: u32,
21}
22
23#[derive(Clone, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct PublicationPlan {
26    pub version: u8,
27    pub chain: Chain,
28    pub author: String,
29    pub root_txid: String,
30    pub total_fee: u64,
31    pub maximum_fee: u64,
32    pub records: Vec<PublicPlan>,
33}
34
35impl PublicationPlan {
36    pub const MAX_BYTES: usize = 128 * 1024 * 1024;
37    pub const MAX_RECORDS: u32 = 1024;
38
39    pub fn id(&self) -> Result<String, Error> {
40        self.validate()?;
41        Ok(hex::encode(Sha256::digest(serde_json::to_vec(self)?)))
42    }
43
44    pub fn validate(&self) -> Result<(), Error> {
45        validation::validate(self)
46    }
47
48    pub fn save_new(&self, path: &Path) -> Result<(), Error> {
49        self.validate()?;
50        let bytes = serde_json::to_vec_pretty(self)?;
51        ensure!(
52            bytes.len() <= Self::MAX_BYTES,
53            "plan exceeds client byte capacity"
54        );
55        storage::write_new(path, &bytes)
56    }
57
58    pub fn load(path: &Path) -> Result<Self, Error> {
59        let plan: Self = serde_json::from_slice(&storage::read_bounded(path, Self::MAX_BYTES)?)?;
60        plan.validate()?;
61        Ok(plan)
62    }
63}
64
65pub fn prepare_atomic(
66    node: &Node,
67    signer: &impl IdentitySigner,
68    record: &PublicRecord,
69    limits: PlanLimits,
70) -> Result<PublicationPlan, Error> {
71    let mut planner = Planner::new(node, signer, limits, 1)?;
72    planner.append(&record.encode()?)?;
73    planner.finish()
74}
75
76pub fn prepare_private_records(
77    node: &Node,
78    signer: &impl IdentitySigner,
79    records: &[Vec<u8>],
80    limits: PlanLimits,
81) -> Result<PublicationPlan, Error> {
82    let mut planner = Planner::new(node, signer, limits, u32::try_from(records.len())?)?;
83    for record in records {
84        urma_core::container::inspect_header(record)?;
85        planner.append(record)?;
86    }
87    planner.finish()
88}
89
90pub fn prepare_multipart(
91    node: &Node,
92    signer: &impl IdentitySigner,
93    payload: &[u8],
94    profile: [u8; 8],
95    limits: PlanLimits,
96) -> Result<PublicationPlan, Error> {
97    let geometry = Geometry::new(u64::try_from(payload.len())?)?;
98    let mut planner = Planner::new(node, signer, limits, geometry.nodes())?;
99    let mut parts = Vec::new();
100    for index in 0..geometry.parts() {
101        let start = usize::try_from(index)? * Geometry::DATA_BYTES;
102        let end = start + geometry.part_length(index)?;
103        let bytes = MultipartRecord::Data(DataPart {
104            index,
105            payload: payload[start..end].to_vec(),
106        })
107        .encode()?;
108        parts.push(planner.append(&bytes)?);
109    }
110    let mut leaves = Vec::new();
111    for (index, entries) in parts.chunks(usize::from(Geometry::FANOUT)).enumerate() {
112        let bytes = MultipartRecord::Leaf(LeafManifest {
113            index: u16::try_from(index)?,
114            entries: entries.to_vec(),
115        })
116        .encode()?;
117        leaves.push(planner.append(&bytes)?);
118    }
119    let root = MultipartRecord::Root(RootManifest {
120        length: u64::try_from(payload.len())?,
121        payload_hash: Sha256::digest(payload).into(),
122        profile,
123        entries: leaves,
124    })
125    .encode()?;
126    planner.append(&root)?;
127    planner.finish()
128}