Skip to main content

urma_runtime/
plan.rs

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