mina_tree/generators/
user_command.rs

1use std::{collections::HashMap, rc::Rc};
2
3use mina_signer::Keypair;
4use rand::Rng;
5
6use crate::{
7    gen_keypair,
8    scan_state::{
9        currency::{Balance, Magnitude},
10        transaction_logic::{
11            for_tests::HashableCompressedPubKey,
12            valid,
13            zkapp_command::{self, verifiable},
14        },
15    },
16    util, Account, AccountId, AuthRequired, BaseLedger, Mask, MyCowMut, Permissions, TokenId,
17    VerificationKey, VerificationKeyWire, ZkAppAccount, TXN_VERSION_CURRENT,
18};
19
20use super::{
21    zkapp_command::GenZkappCommandParams, Failure, Role, LEDGER_DEPTH, MAX_ACCOUNT_UPDATES,
22    MAX_TOKEN_UPDATES, MINIMUM_USER_COMMAND_FEE,
23};
24
25fn zkapp_command_with_ledger(
26    num_keypairs: Option<usize>,
27    max_account_updates: Option<usize>,
28    max_token_updates: Option<usize>,
29    account_state_tbl: Option<&mut HashMap<AccountId, (Account, Role)>>,
30    vk: Option<VerificationKeyWire>,
31    failure: Option<&Failure>,
32) -> (
33    valid::UserCommand,
34    Keypair,
35    HashMap<HashableCompressedPubKey, Keypair>,
36    Mask,
37) {
38    let mut rng = rand::thread_rng();
39
40    // Need a fee payer keypair, a keypair for the "balancing" account (so that the balance changes
41    // sum to zero), and max_account_updates * 2 keypairs, because all the other zkapp_command
42    // might be new and their accounts not in the ledger; or they might all be old and in the ledger
43
44    // We'll put the fee payer account and max_account_updates accounts in the
45    // ledger, and have max_account_updates keypairs available for new accounts
46    let max_account_updates = max_account_updates.unwrap_or(MAX_ACCOUNT_UPDATES);
47    let max_token_updates = max_token_updates.unwrap_or(MAX_TOKEN_UPDATES);
48    let num_keypairs =
49        num_keypairs.unwrap_or((max_account_updates * 2) + (max_token_updates * 3) + 2);
50
51    let keypairs: Vec<Keypair> = (0..num_keypairs).map(|_| gen_keypair()).collect();
52
53    let keymap: HashMap<HashableCompressedPubKey, Keypair> = keypairs
54        .iter()
55        .map(|kp| {
56            let compressed = kp.public.into_compressed();
57            (HashableCompressedPubKey(compressed), kp.clone())
58        })
59        .collect();
60
61    let num_keypairs_in_ledger = num_keypairs / 2;
62    let keypairs_in_ledger = util::take(&keypairs, num_keypairs_in_ledger);
63
64    let account_ids: Vec<AccountId> = keypairs_in_ledger
65        .iter()
66        .map(|Keypair { public, .. }| {
67            AccountId::create(public.into_compressed(), TokenId::default())
68        })
69        .collect();
70
71    let verification_key = vk.clone().unwrap_or_else(|| {
72        let dummy_vk = VerificationKey::dummy();
73        VerificationKeyWire::new((*dummy_vk).clone())
74    });
75
76    let balances: Vec<Balance> = {
77        let min_cmd_fee = MINIMUM_USER_COMMAND_FEE;
78
79        let min_balance = {
80            let balance = min_cmd_fee.as_u64() + 100_000_000_000_000_000;
81            Balance::from_u64(balance)
82        };
83
84        // max balance to avoid overflow when adding deltas
85        let max_balance = {
86            let max_bal = Balance::of_mina_string_exn("2000000000.0");
87
88            assert_eq!(max_bal.as_u64(), 2000000000000000000);
89
90            min_balance
91                .checked_add(&max_bal)
92                .expect("zkapp_command_with_ledger: overflow for max_balance")
93        };
94
95        (0..num_keypairs_in_ledger)
96            .map(move |_| {
97                let balance = rng.gen_range(min_balance.as_u64()..max_balance.as_u64());
98                Balance::from_u64(balance)
99            })
100            .collect()
101    };
102
103    let account_ids_and_balances: Vec<(AccountId, Balance)> =
104        account_ids.iter().cloned().zip(balances).collect();
105
106    let snappify_account = |mut account: Account| {
107        let permissions = Permissions {
108            edit_state: AuthRequired::Either,
109            send: AuthRequired::Either,
110            set_delegate: AuthRequired::Either,
111            set_permissions: AuthRequired::Either,
112            set_verification_key: crate::SetVerificationKey {
113                auth: AuthRequired::Either,
114                txn_version: TXN_VERSION_CURRENT,
115            },
116            set_zkapp_uri: AuthRequired::Either,
117            edit_action_state: AuthRequired::Either,
118            set_token_symbol: AuthRequired::Either,
119            increment_nonce: AuthRequired::Either,
120            set_voting_for: AuthRequired::Either,
121            set_timing: AuthRequired::Either,
122            //receive: AuthRequired::Either,
123            ..Permissions::user_default()
124        };
125
126        let verification_key = Some(verification_key.clone());
127        let zkapp = Some(
128            ZkAppAccount {
129                verification_key,
130                ..ZkAppAccount::default()
131            }
132            .into(),
133        );
134
135        account.zkapp = zkapp;
136        account.permissions = permissions;
137
138        account
139    };
140
141    // half zkApp accounts, half non-zkApp accounts
142    let accounts =
143        account_ids_and_balances
144            .iter()
145            .enumerate()
146            .map(|(ndx, (account_id, balance))| {
147                let account = Account::create_with(account_id.clone(), *balance);
148                if ndx % 2 == 0 {
149                    account
150                } else {
151                    snappify_account(account)
152                }
153            });
154
155    let fee_payer_keypair = keypairs.first().unwrap();
156
157    let mut ledger = Mask::create(LEDGER_DEPTH);
158
159    account_ids.iter().zip(accounts).for_each(|(id, account)| {
160        let res = ledger
161            .get_or_create_account(id.clone(), account)
162            .expect("zkapp_command: error adding account for account id");
163        assert!(
164            matches!(res, crate::GetOrCreated::Added(_)),
165            "zkapp_command: account for account id already exists"
166        );
167    });
168
169    // to keep track of account states across transactions
170    let mut account_state_tbl = match account_state_tbl {
171        Some(account_state_tbl) => MyCowMut::Borrow(account_state_tbl),
172        None => MyCowMut::Own(HashMap::new()),
173    };
174    let account_state_tbl = Some(&mut *account_state_tbl);
175
176    let zkapp_command =
177        super::zkapp_command::gen_zkapp_command_from(super::zkapp_command::GenZkappCommandParams {
178            failure,
179            max_account_updates: Some(max_account_updates),
180            max_token_updates: Some(max_token_updates),
181            fee_payer_keypair,
182            keymap: &keymap,
183            account_state_tbl,
184            ledger: ledger.clone(),
185            protocol_state_view: None,
186            vk: vk.as_ref(),
187            global_slot: None,
188        });
189
190    use crate::scan_state::transaction_logic::TransactionStatus::Applied;
191
192    let zkapp_command =
193        zkapp_command::valid::to_valid(zkapp_command, &Applied, |hash, account_id| {
194            verifiable::find_vk_via_ledger(ledger.clone(), hash, account_id)
195        })
196        .unwrap();
197    let user_command = valid::UserCommand::ZkAppCommand(Box::new(zkapp_command));
198
199    // include generated ledger in result
200    (user_command, fee_payer_keypair.clone(), keymap, ledger)
201}
202
203/// <https://github.com/MinaProtocol/mina/blob/3753a8593cc1577bcf4da16620daf9946d88e8e5/src/lib/mina_generators/user_command_generators.ml#L146>
204pub fn sequence_zkapp_command_with_ledger(
205    max_account_updates: Option<usize>,
206    max_token_updates: Option<usize>,
207    length: Option<usize>,
208    vk: Option<VerificationKeyWire>,
209    failure: Option<&Failure>,
210) -> (
211    Vec<(
212        valid::UserCommand,
213        Rc<Keypair>,
214        Rc<HashMap<HashableCompressedPubKey, Keypair>>,
215    )>,
216    Mask,
217) {
218    let mut rng = rand::thread_rng();
219
220    let length = length.unwrap_or_else(|| rng.gen::<usize>() % 100);
221    let max_account_updates = max_account_updates.unwrap_or(MAX_ACCOUNT_UPDATES);
222    let max_token_updates = max_token_updates.unwrap_or(MAX_TOKEN_UPDATES);
223
224    let num_keypairs = length * max_account_updates * 2;
225
226    // Keep track of account states across multiple zkapp_command transaction
227    let mut account_state_tbl = HashMap::<AccountId, (Account, Role)>::with_capacity(64);
228
229    let num_keypairs = Some(num_keypairs);
230    let max_account_updates = Some(max_account_updates);
231    let max_token_updates = Some(max_token_updates);
232    // let account_state_tbl = Some(&mut account_state_tbl);
233
234    let (zkapp_command, fee_payer_keypair, keymap, ledger) = zkapp_command_with_ledger(
235        num_keypairs,
236        max_account_updates,
237        max_token_updates,
238        Some(&mut account_state_tbl),
239        vk.clone(),
240        failure,
241    );
242
243    let fee_payer_keypair = Rc::new(fee_payer_keypair);
244    let keymap = Rc::new(keymap);
245
246    let mut commands = Vec::with_capacity(length);
247
248    commands.push((
249        zkapp_command,
250        Rc::clone(&fee_payer_keypair),
251        Rc::clone(&keymap),
252    ));
253
254    (0..length.saturating_sub(1)).for_each(|_| {
255        let zkapp_command = super::zkapp_command::gen_zkapp_command_from(GenZkappCommandParams {
256            failure,
257            max_account_updates,
258            max_token_updates,
259            fee_payer_keypair: &fee_payer_keypair,
260            keymap: &keymap,
261            account_state_tbl: Some(&mut account_state_tbl),
262            ledger: ledger.clone(),
263            protocol_state_view: None,
264            vk: vk.as_ref(),
265            global_slot: None,
266        });
267
268        use crate::scan_state::transaction_logic::TransactionStatus::Applied;
269        let zkapp_command =
270            zkapp_command::valid::to_valid(zkapp_command, &Applied, |hash, account_id| {
271                verifiable::find_vk_via_ledger(ledger.clone(), hash, account_id)
272            })
273            .unwrap();
274        let zkapp_command = valid::UserCommand::ZkAppCommand(Box::new(zkapp_command));
275
276        commands.push((
277            zkapp_command,
278            Rc::clone(&fee_payer_keypair),
279            Rc::clone(&keymap),
280        ));
281    });
282
283    (commands, ledger)
284}