1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
use crate::storage_contract::{ProofStatus, STORAGE_ACCOUNT_SPACE};
use crate::{id, rewards_pools};
use serde_derive::{Deserialize, Serialize};
use solana_sdk::hash::Hash;
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature;
use solana_sdk::system_instruction;
use solana_sdk::sysvar::{clock, rewards};

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub enum StorageAccountType {
    Archiver,
    Validator,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum StorageInstruction {
    /// Initialize the account as a validator or archiver
    ///
    /// Expects 1 Account:
    ///    0 - Account to be initialized
    InitializeStorage {
        owner: Pubkey,
        account_type: StorageAccountType,
    },

    SubmitMiningProof {
        sha_state: Hash,
        segment_index: u64,
        signature: Signature,
        blockhash: Hash,
    },
    AdvertiseStorageRecentBlockhash {
        hash: Hash,
        segment: u64,
    },
    /// Redeem storage reward credits
    ///
    /// Expects 1 Account:
    ///    0 - Storage account with credits to redeem
    ///    1 - Clock Syscall to figure out the clock epoch
    ///    2 - Archiver account to credit - this account *must* be the owner
    ///    3 - MiningPool account to redeem credits from
    ///    4 - Rewards Syscall to figure out point values
    ClaimStorageReward,
    ProofValidation {
        /// The segment during which this proof was generated
        segment: u64,
        /// A Vec of proof masks per keyed archiver account loaded by the instruction
        proofs: Vec<Vec<ProofStatus>>,
    },
}

fn get_ratios() -> (u64, u64) {
    // max number bytes available for account metas and proofs
    // The maximum transaction size is == `PACKET_DATA_SIZE` (1232 bytes)
    // There are approx. 900 bytes left over after the storage instruction is wrapped into
    // a signed transaction.
    static MAX_BYTES: u64 = 900;
    let account_meta_size: u64 =
        bincode::serialized_size(&AccountMeta::new(Pubkey::new_rand(), false)).unwrap_or(0);
    let proof_size: u64 = bincode::serialized_size(&ProofStatus::default()).unwrap_or(0);

    // the ratio between account meta size and a single proof status
    let ratio = (account_meta_size + proof_size - 1) / proof_size;
    let bytes = (MAX_BYTES + ratio - 1) / ratio;
    (ratio, bytes)
}

/// Returns how many accounts and their proofs will fit in a single proof validation tx
///
/// # Arguments
///
/// * `proof_mask_max` - The largest proof mask across all accounts intended for submission
///
pub fn validation_account_limit(proof_mask_max: usize) -> u64 {
    let (ratio, bytes) = get_ratios();
    // account_meta_count * (ratio + proof_mask_max) = bytes
    bytes / (ratio + proof_mask_max as u64)
}

pub fn proof_mask_limit() -> u64 {
    let (ratio, bytes) = get_ratios();
    bytes - ratio
}

pub fn create_storage_account(
    from_pubkey: &Pubkey,
    storage_owner: &Pubkey,
    storage_pubkey: &Pubkey,
    lamports: u64,
    account_type: StorageAccountType,
) -> Vec<Instruction> {
    vec![
        system_instruction::create_account(
            from_pubkey,
            storage_pubkey,
            lamports,
            STORAGE_ACCOUNT_SPACE,
            &id(),
        ),
        Instruction::new(
            id(),
            &StorageInstruction::InitializeStorage {
                owner: *storage_owner,
                account_type,
            },
            vec![AccountMeta::new(*storage_pubkey, false)],
        ),
    ]
}

pub fn mining_proof(
    storage_pubkey: &Pubkey,
    sha_state: Hash,
    segment_index: u64,
    signature: Signature,
    blockhash: Hash,
) -> Instruction {
    let storage_instruction = StorageInstruction::SubmitMiningProof {
        sha_state,
        segment_index,
        signature,
        blockhash,
    };
    let account_metas = vec![
        AccountMeta::new(*storage_pubkey, true),
        AccountMeta::new(clock::id(), false),
    ];
    Instruction::new(id(), &storage_instruction, account_metas)
}

pub fn advertise_recent_blockhash(
    storage_pubkey: &Pubkey,
    storage_hash: Hash,
    segment: u64,
) -> Instruction {
    let storage_instruction = StorageInstruction::AdvertiseStorageRecentBlockhash {
        hash: storage_hash,
        segment,
    };
    let account_metas = vec![
        AccountMeta::new(*storage_pubkey, true),
        AccountMeta::new(clock::id(), false),
    ];
    Instruction::new(id(), &storage_instruction, account_metas)
}

pub fn proof_validation(
    storage_pubkey: &Pubkey,
    segment: u64,
    checked_proofs: Vec<(Pubkey, Vec<ProofStatus>)>,
) -> Instruction {
    let mut account_metas = vec![
        AccountMeta::new(*storage_pubkey, true),
        AccountMeta::new(clock::id(), false),
    ];
    let mut proofs = vec![];
    checked_proofs.into_iter().for_each(|(id, p)| {
        proofs.push(p);
        account_metas.push(AccountMeta::new(id, false))
    });
    let storage_instruction = StorageInstruction::ProofValidation { segment, proofs };
    Instruction::new(id(), &storage_instruction, account_metas)
}

pub fn claim_reward(owner_pubkey: &Pubkey, storage_pubkey: &Pubkey) -> Instruction {
    let storage_instruction = StorageInstruction::ClaimStorageReward;
    let account_metas = vec![
        AccountMeta::new(*storage_pubkey, false),
        AccountMeta::new(clock::id(), false),
        AccountMeta::new(rewards::id(), false),
        AccountMeta::new(rewards_pools::random_id(), false),
        AccountMeta::new(*owner_pubkey, false),
    ];
    Instruction::new(id(), &storage_instruction, account_metas)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_size() {
        // check that if there's 50 proof per account, only 1 account can fit in a single tx
        assert_eq!(validation_account_limit(50), 1);
    }
}