shadow_drive_user_staking/instructions/
store_file.rs

1use crate::constants::*;
2use crate::errors::ErrorCodes;
3use crate::instructions::{
4    initialize_account::{StorageAccount, UserInfo},
5    initialize_config::StorageConfig,
6};
7use anchor_lang::prelude::*;
8use anchor_spl::token::Mint;
9use std::convert::TryInto;
10
11// Discriminator + seed u64 + time i64 + booleans + size u64 + sha256 + txhash + name + url
12pub const FILE_ACCOUNT_SIZE: usize = 8 // discriminator
13    + 4 // delete request epoch
14    + 8 // size
15    + 1 // bools
16    + 4 // seed
17    + SHA256_HASH_SIZE
18    + 4 + MAX_FILENAME_SIZE
19    + 32; // storage_account pubkey
20          // pub fn file_account_size(
21          //     filename: &String
22          // ) -> usize
23          // {
24          //     8 // discriminator
25          //     + 4 // seed u32
26          //     + 4 // delete_request_epoch u64
27          //     + 1 // booleans
28          //     + 8 // file size
29          //     + SHA256_HASH_SIZE
30          //     + filename.as_bytes().len()
31          // }
32          //    + MAX_URL_SIZE;
33
34/// This is the function that handles the `store_file` ix
35pub fn handler(
36    ctx: Context<StoreFile>,
37    filename: String,
38    //url: String,
39    size: u64,
40    // created: i64,
41    sha256_hash: String,
42) -> Result<()> {
43    // Ensure this user has never had a bad_csam
44    require!(
45        !ctx.accounts.user_info.lifetime_bad_csam,
46        ErrorCodes::HasHadBadCsam
47    );
48
49    // Ensure account is not immutable
50    require!(
51        !ctx.accounts.storage_account.immutable,
52        ErrorCodes::StorageAccountMarkedImmutable
53    );
54
55    msg!("Initializing child File account: {}", filename);
56    {
57        let file = &mut ctx.accounts.file;
58
59        // Initialize as mutable
60        file.immutable = false;
61
62        // Initialize deletion flag
63        file.to_be_deleted = false;
64
65        // Initialize delete request time
66        file.delete_request_epoch = Clock::get()?.epoch.try_into().unwrap();
67
68        // Store file size
69        // NOTE: Now that we are not tracking storage on-chain in v1.5, this is the wrong condition,
70        // as it should check storage_available > size. It is up to the uploader server to 
71        // check this condition! For now, we do this minimal sanity check whether the file
72        // is smaller than the total storage on-chain.
73        require_gte!(
74            ctx.accounts.storage_account.storage,
75            size,
76            ErrorCodes::NotEnoughStorage
77        );
78        file.size = size;
79
80        // Store sha256 hash
81        file.store_sha256(&sha256_hash);
82
83        // Store storage account
84        file.storage_account = ctx.accounts.storage_account.key();
85
86        // Store and increment file counter seed
87        file.init_counter_seed = ctx.accounts.storage_account.init_counter;
88        ctx.accounts.storage_account.init_counter = ctx.accounts.storage_account.init_counter
89            .checked_add(1)
90            .unwrap();
91
92        // Store file name
93        require!(
94            filename.as_bytes().len() <= MAX_FILENAME_SIZE,
95            ErrorCodes::FileNameLengthExceedsLimit
96        );
97        file.name = filename;
98
99        // Store file URL
100        //file.url = url;
101    }
102
103    // No longer done on chain as of v1.5
104    // msg!(
105    //     "Updating storage on parent StorageAccount: {}",
106    //     ctx.accounts.storage_account.identifier
107    // );
108    // {
109    //     let storage_account = &mut ctx.accounts.storage_account;
110
111    //     // Decrease storage available
112    //     storage_account.storage_available =
113    //         validate_storage_available_sub(storage_account.storage_available, size)?;
114    // }
115
116    Ok(())
117}
118
119#[derive(Accounts)]
120#[instruction(filename: String)]
121/// This `StoreFile` context is used in the instruction which allows users to
122/// store a file after our uploader server keypair signs off, verifying all is well.
123pub struct StoreFile<'info> {
124    /// This is the `StorageConfig` accounts that holds all of the admin and uploader keys.
125    /// Requires mutability to update global storage counter.
126    #[account(
127        mut,
128        seeds = [
129            "storage-config".as_bytes()
130        ],
131        bump,
132    )]
133    pub storage_config: Box<Account<'info, StorageConfig>>,
134
135    /// Parent storage account, which should already be initialized.
136    /// Requires mutability to update user storage account storage counter.
137    #[account(
138        mut,
139        seeds = [
140            "storage-account".as_bytes(),
141            &storage_account.owner_1.key().to_bytes(),
142            &storage_account.account_counter_seed.to_le_bytes()
143        ],
144        bump,
145    )]
146    pub storage_account: Box<Account<'info, StorageAccount>>,
147
148    // Child file account, to be initialized.
149    #[account(
150        init,
151        payer = owner,
152        space = FILE_ACCOUNT_SIZE,
153        seeds = [
154            storage_account.key().to_bytes().as_ref(),
155            &storage_account.init_counter.to_le_bytes(),
156        ],
157        bump,
158    )]
159    pub file: Account<'info, File>,
160
161    // Account containing user info
162    #[account(
163        mut,
164        seeds = [
165            "user-info".as_bytes(),
166            &storage_account.owner_1.key().to_bytes(),
167        ],
168        bump,
169    )]
170    pub user_info: Box<Account<'info, UserInfo>>,
171
172    /// File owner (user).
173    /// Requires mutability since owner/user is fee payer.
174    #[account(mut, constraint = is_owner(&owner, &storage_account))]
175    pub owner: Signer<'info>,
176
177    /// Uploader needs to sign to ensure all is well on storage server (incl CSAM scan).
178    #[account(constraint = uploader.key() == storage_config.uploader)]
179    pub uploader: Signer<'info>,
180
181    /// Token mint account
182    #[account(mut, address = shdw::ID)]
183    pub token_mint: Account<'info, Mint>,
184
185    /// System Program
186    pub system_program: Program<'info, System>,
187}
188
189#[account]
190pub struct File {
191    /// Mutability
192    pub immutable: bool,
193
194    /// Delete flag
195    pub to_be_deleted: bool,
196
197    /// Delete request epoch
198    pub delete_request_epoch: u32,
199
200    /// File size (bytes)
201    pub size: u64,
202
203    /// File hash (sha256)
204    pub sha256_hash: [u8; 32],
205
206    /// File counter seed
207    pub init_counter_seed: u32,
208
209    /// Storage accout
210    pub storage_account: Pubkey,
211
212    /// File name
213    pub name: String,
214    // /// File url
215    // pub url: String,
216}
217
218impl File {
219    /// This function takes in a reference to a string or &str and turns it into a more compact byte array.
220    /// A sha256 hash only needs 32 bytes to be stored, but takes up 64 bytes when stored with utf-8.
221    pub fn store_sha256(&mut self, hash_string: &String) {
222        let string_bytes = hex::decode(hash_string);
223        self.sha256_hash = {
224            if string_bytes.is_ok() {
225                let string_bytes = string_bytes.unwrap();
226                if string_bytes.len() == SHA256_HASH_SIZE {
227                    Ok(string_bytes.try_into().unwrap())
228                } else {
229                    err!(ErrorCodes::InvalidSha256Hash).into()
230                }
231            } else {
232                err!(ErrorCodes::InvalidSha256Hash).into()
233            }
234        }
235        .unwrap();
236    }
237
238    // /// This function takes in a reference to a string or &str and turns it into a more compact byte array.
239    // pub fn store_ceph(&mut self, hash_string: &String) {
240    //     let string_bytes = hex::decode(hash_string);
241    //     self.ceph_hash = {
242    //         if string_bytes.is_ok() {
243    //             let string_bytes = string_bytes.unwrap();
244    //             if string_bytes.len() == CEPH_HASH_SIZE {
245    //                 Ok(string_bytes.try_into().unwrap())
246    //             } else {
247    //                 err!(ErrorCodes::InvalidCEPHHash).into()
248    //             }
249    //         } else {
250    //             err!(ErrorCodes::InvalidCEPHHash).into()
251    //         }
252    //     }
253    //     .unwrap();
254    // }
255}
256
257pub fn validate_storage_available_sub(storage_available: u64, size: u64) -> Result<u64> {
258    let result = storage_available.checked_sub(size);
259    if let Some(valid) = result {
260        Ok(valid)
261    } else {
262        err!(ErrorCodes::NotEnoughStorage)
263    }
264}
265
266pub fn is_owner<'info>(
267    owner: &AccountInfo<'info>,
268    storage_account: &Account<'info, StorageAccount>,
269) -> bool {
270    owner.key() == storage_account.owner_1 || owner.key() == storage_account.owner_2
271    // || owner.key() == storage_account.owner_3
272    // || owner.key() == storage_account.owner_4
273}