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            "{JUNO_CDN_PROPOSALS_ERROR_EMPTY_ASSETS} ({proposal_id})"
99        ));
100    }
101
102    for (key, asset) in assets {
103        insert_asset(cdn_heap, &key.full_path, &asset);
104
105        for (encoding_type, encoding) in asset.encodings {
106            let mut content_chunks = Vec::new();
107
108            for (i, _) in encoding.content_chunks.iter().enumerate() {
109                let chunks = get_content_chunks(cdn_stable, &encoding, i).ok_or_else(|| {
110                    format!(
111                        "{JUNO_CDN_PROPOSALS_ERROR_NOT_CONTENT_CHUNKS_AT_INDEX} ({encoding_type} - {i})."
112                    )
113                })?;
114
115                content_chunks.push(chunks);
116            }
117
118            if content_chunks.is_empty() {
119                return Err(format!(
120                    "{JUNO_CDN_PROPOSALS_ERROR_EMPTY_CONTENT_CHUNKS} ({encoding_type})"
121                ));
122            }
123
124            let encoding_with_content = AssetEncoding {
125                content_chunks,
126                ..encoding
127            };
128
129            insert_asset_encoding(
130                cdn_heap,
131                &key.full_path,
132                &encoding_type,
133                &encoding_with_content,
134            )?;
135        }
136    }
137
138    Ok(())
139}