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}