mina_tree/generators/
user_command.rs

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