urma-runtime 0.3.0

URMA publication planning, network transports and recovery
Documentation
use crate::config;
use crate::error::{Context, Error, ensure};
use crate::publication::{PublicPlan, prepare_signed_bytes};
use crate::transaction::decode;
use crate::wallet::FundingInput;
use crate::{
    node::Node,
    plan::{PlanLimits, PublicationPlan},
};
use sha2::{Digest, Sha256};
use std::num::NonZeroU64;
use urma_core::multipart::ChildReference;
use urma_identity::identity::IdentitySigner;
use urma_wallet::{
    account::{Account, KeyPath},
    funding::Funding,
    wallet::{FeeBudget, FeeRate},
};

pub(crate) struct Planner<'a, A> {
    author: &'a A,
    funder: &'a Account,
    inputs: Vec<FundingInput>,
    plan: PublicationPlan,
    limits: PlanLimits,
}

impl<'a, A: IdentitySigner> Planner<'a, A> {
    pub(crate) fn new(
        node: &Node,
        author: &'a A,
        funder: &'a Account,
        limits: PlanLimits,
        records: u32,
    ) -> Result<Self, Error> {
        ensure!(
            records <= PublicationPlan::MAX_RECORDS,
            "in-memory publication exceeds client capacity"
        );
        Self::streaming(node, author, funder, limits, records)
    }

    pub(crate) fn streaming(
        node: &Node,
        author: &'a A,
        funder: &'a Account,
        limits: PlanLimits,
        records: u32,
    ) -> Result<Self, Error> {
        ensure!(
            records > 0 && records <= limits.max_records,
            "publication exceeds caller record budget"
        );
        ensure!(
            (1..=100).contains(&limits.fee_rate) && limits.max_fee > 0,
            "invalid publication fee limits"
        );
        let minimum = limits
            .max_fee
            .checked_add((u64::from(records) + 1) * config::publication_return(node.chain()))
            .context("funding budget overflow")?;
        let selection = node.select_coins(funder, minimum, 0)?;
        Ok(Self {
            author,
            funder,
            inputs: selection.inputs,
            limits,
            plan: PublicationPlan {
                version: 1,
                chain: node.chain(),
                author: author.public_key().inner.x_only_public_key().0.to_string(),
                return_script: hex::encode(funder.receive_script()?.as_bytes()),
                root_txid: String::new(),
                total_fee: 0,
                maximum_fee: limits.max_fee,
                records: Vec::new(),
            },
        })
    }

    pub(crate) fn append(&mut self, record: &[u8]) -> Result<ChildReference, Error> {
        let (reference, pair) = self.prepare_record(record)?;
        self.plan.records.push(pair);
        Ok(reference)
    }

    pub(crate) fn prepare_record(
        &mut self,
        record: &[u8],
    ) -> Result<(ChildReference, PublicPlan), Error> {
        let budget = FeeBudget {
            chain: self.plan.chain.genesis()?,
            rate: FeeRate(NonZeroU64::new(self.limits.fee_rate).context("zero fee rate")?),
            maximum_base_units: self.limits.max_fee,
        };
        let prepared =
            prepare_signed_bytes(record, self.author, self.funder, &self.inputs, budget)?;
        let pair = prepared.plan;
        let reveal = decode(
            &pair.reveal,
            usize::try_from(config::STANDARD_TX_WEIGHT)? * 2,
        )?;
        let total_fee = self
            .plan
            .total_fee
            .checked_add(prepared.fee)
            .context("fee sum overflow")?;
        ensure!(
            total_fee <= self.limits.max_fee,
            "publication exceeds approved maximum fee"
        );
        self.plan.total_fee = total_fee;
        self.inputs = vec![FundingInput {
            funding: Funding {
                raw_transaction: pair.commit.clone(),
                vout: 1,
            },
            path: KeyPath::RECEIVE,
        }];
        self.plan.root_txid = reveal.compute_txid().to_string();
        let reference = ChildReference {
            txid: reveal.compute_txid(),
            record_hash: Sha256::digest(record).into(),
        };
        Ok((reference, pair))
    }

    pub(crate) fn metadata(&self) -> &PublicationPlan {
        &self.plan
    }

    pub(crate) fn finish(self) -> Result<PublicationPlan, Error> {
        self.plan.validate()?;
        Ok(self.plan)
    }
}