junobuild_cdn/proposals/workflows/
commit.rs

1use crate::proposals::errors::{
2    JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT, JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT_INVALID_STATUS,
3    JUNO_CDN_PROPOSALS_ERROR_EMPTY_ASSETS, JUNO_CDN_PROPOSALS_ERROR_EMPTY_CONTENT_CHUNKS,
4    JUNO_CDN_PROPOSALS_ERROR_INVALID_HASH, JUNO_CDN_PROPOSALS_ERROR_NOT_CONTENT_CHUNKS_AT_INDEX,
5};
6use crate::proposals::workflows::assert::assert_known_proposal_type;
7use crate::proposals::{get_proposal, insert_proposal};
8use crate::proposals::{CommitProposal, CommitProposalError, Proposal, ProposalId, ProposalStatus};
9use crate::storage::heap::insert_asset;
10use crate::storage::heap::store::insert_asset_encoding;
11use crate::storage::stable::{get_assets, get_content_chunks};
12use crate::strategies::{CdnHeapStrategy, CdnStableStrategy, CdnWorkflowStrategy};
13use hex::encode;
14use junobuild_storage::types::store::AssetEncoding;
15
16pub fn commit_proposal(
17    cdn_heap: &impl CdnHeapStrategy,
18    cdn_stable: &impl CdnStableStrategy,
19    cdn_workflow: &impl CdnWorkflowStrategy,
20    proposition: &CommitProposal,
21) -> Result<(), CommitProposalError> {
22    let proposal = get_proposal(cdn_stable, &proposition.proposal_id).ok_or_else(|| {
23        CommitProposalError::ProposalNotFound(format!(
24            "{} ({})",
25            JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT, proposition.proposal_id
26        ))
27    })?;
28
29    match secure_commit_proposal(cdn_heap, cdn_stable, cdn_workflow, proposition, &proposal) {
30        Ok(_) => {
31            let executed_proposal = Proposal::execute(&proposal);
32            insert_proposal(cdn_stable, &proposition.proposal_id, &executed_proposal);
33            Ok(())
34        }
35        Err(e @ CommitProposalError::CommitAssetsIssue(_))
36        | Err(e @ CommitProposalError::PostCommitAssetsIssue(_)) => {
37            let failed_proposal = Proposal::fail(&proposal);
38            insert_proposal(cdn_stable, &proposition.proposal_id, &failed_proposal);
39            Err(e)
40        }
41        Err(e) => Err(e),
42    }
43}
44
45fn secure_commit_proposal(
46    cdn_heap: &impl CdnHeapStrategy,
47    cdn_stable: &impl CdnStableStrategy,
48    cdn_workflow: &impl CdnWorkflowStrategy,
49    commit_proposal: &CommitProposal,
50    proposal: &Proposal,
51) -> Result<(), CommitProposalError> {
52    if proposal.status != ProposalStatus::Open {
53        return Err(CommitProposalError::ProposalNotOpen(format!(
54            "{} ({:?})",
55            JUNO_CDN_PROPOSALS_ERROR_CANNOT_COMMIT_INVALID_STATUS, proposal.status
56        )));
57    }
58
59    match &proposal.sha256 {
60        Some(sha256) if sha256 == &commit_proposal.sha256 => (),
61        _ => {
62            return Err(CommitProposalError::InvalidSha256(format!(
63                "{} ({})",
64                JUNO_CDN_PROPOSALS_ERROR_INVALID_HASH,
65                encode(commit_proposal.sha256)
66            )));
67        }
68    }
69
70    assert_known_proposal_type(proposal).map_err(CommitProposalError::InvalidType)?;
71
72    // Mark proposal as accepted.
73    let accepted_proposal = Proposal::accept(proposal);
74    insert_proposal(cdn_stable, &commit_proposal.proposal_id, &accepted_proposal);
75
76    cdn_workflow.pre_commit_assets(proposal);
77
78    copy_committed_assets(cdn_heap, cdn_stable, &commit_proposal.proposal_id)
79        .map_err(CommitProposalError::CommitAssetsIssue)?;
80
81    cdn_workflow
82        .post_commit_assets(proposal)
83        .map_err(CommitProposalError::PostCommitAssetsIssue)?;
84
85    Ok(())
86}
87
88fn copy_committed_assets(
89    cdn_heap: &impl CdnHeapStrategy,
90    cdn_stable: &impl CdnStableStrategy,
91    proposal_id: &ProposalId,
92) -> Result<(), String> {
93    // Copy from stable memory to heap.
94    let assets = get_assets(cdn_stable, proposal_id);
95
96    if assets.is_empty() {
97        return Err(format!(
98            "{} ({})",
99            JUNO_CDN_PROPOSALS_ERROR_EMPTY_ASSETS, proposal_id
100        ));
101    }
102
103    for (key, asset) in assets {
104        insert_asset(cdn_heap, &key.full_path, &asset);
105
106        for (encoding_type, encoding) in asset.encodings {
107            let mut content_chunks = Vec::new();
108
109            for (i, _) in encoding.content_chunks.iter().enumerate() {
110                let chunks = get_content_chunks(cdn_stable, &encoding, i).ok_or_else(|| {
111                    format!(
112                        "{} ({} - {}).",
113                        JUNO_CDN_PROPOSALS_ERROR_NOT_CONTENT_CHUNKS_AT_INDEX, encoding_type, i
114                    )
115                })?;
116
117                content_chunks.push(chunks);
118            }
119
120            if content_chunks.is_empty() {
121                return Err(format!(
122                    "{} ({})",
123                    JUNO_CDN_PROPOSALS_ERROR_EMPTY_CONTENT_CHUNKS, encoding_type
124                ));
125            }
126
127            let encoding_with_content = AssetEncoding {
128                content_chunks,
129                ..encoding
130            };
131
132            insert_asset_encoding(
133                cdn_heap,
134                &key.full_path,
135                &encoding_type,
136                &encoding_with_content,
137            )?;
138        }
139    }
140
141    Ok(())
142}