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