Skip to main content

mina_tree/scan_state/transaction_logic/
for_tests.rs

1use super::{
2    zkapp_command, Account, AccountId, Amount, Balance, Fee, Memo, Nonce, TokenId,
3    VerificationKeyWire,
4};
5use crate::{
6    gen_keypair,
7    scan_state::{currency::Magnitude, parallel_scan::ceil_log2},
8    sparse_ledger::LedgerIntf,
9    AuthRequired, BaseLedger, Mask, Permissions, VerificationKey, ZkAppAccount,
10    TXN_VERSION_CURRENT,
11};
12use mina_curves::pasta::Fp;
13use mina_signer::{CompressedPubKey, Keypair};
14use rand::Rng;
15use std::collections::{HashMap, HashSet};
16
17const MIN_INIT_BALANCE: u64 = 8000000000;
18const MAX_INIT_BALANCE: u64 = 8000000000000;
19const NUM_ACCOUNTS: u64 = 10;
20const NUM_TRANSACTIONS: u64 = 10;
21const DEPTH: u64 = ceil_log2(NUM_ACCOUNTS + NUM_TRANSACTIONS);
22
23/// Use this for tests only
24/// Hashmaps are not deterministic
25#[derive(Debug, PartialEq, Eq)]
26pub struct HashableKeypair(pub Keypair);
27
28impl std::hash::Hash for HashableKeypair {
29    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30        let compressed = self.0.public.into_compressed();
31        HashableCompressedPubKey(compressed).hash(state);
32    }
33}
34
35/// Use this for tests only
36/// Hashmaps are not deterministic
37#[derive(Clone, Debug, Eq, derive_more::From)]
38pub struct HashableCompressedPubKey(pub CompressedPubKey);
39
40impl PartialEq for HashableCompressedPubKey {
41    fn eq(&self, other: &Self) -> bool {
42        self.0 == other.0
43    }
44}
45
46impl std::hash::Hash for HashableCompressedPubKey {
47    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
48        self.0.x.hash(state);
49        self.0.is_odd.hash(state);
50    }
51}
52
53impl PartialOrd for HashableCompressedPubKey {
54    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
55        match self.0.x.partial_cmp(&other.0.x) {
56            Some(core::cmp::Ordering::Equal) => {}
57            ord => return ord,
58        };
59        self.0.is_odd.partial_cmp(&other.0.is_odd)
60    }
61}
62
63/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/transaction_logic/mina_transaction_logic.ml#L2285-L2285>
64#[derive(Debug)]
65pub struct InitLedger(pub Vec<(Keypair, u64)>);
66
67/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/transaction_logic/mina_transaction_logic.ml#L2351-L2356>
68#[derive(Debug)]
69pub struct TransactionSpec {
70    pub fee: Fee,
71    pub sender: (Keypair, Nonce),
72    pub receiver: CompressedPubKey,
73    pub amount: Amount,
74}
75
76/// OCaml: <https://github.com/MinaProtocol/mina/blob/5da42ccd72e791f164d4d200cf1ce300262873b3/src/lib/transaction_logic/mina_transaction_logic.ml#L2407>
77#[derive(Debug)]
78pub struct TestSpec {
79    pub init_ledger: InitLedger,
80    pub specs: Vec<TransactionSpec>,
81}
82
83impl InitLedger {
84    pub fn init(&self, zkapp: Option<bool>, ledger: &mut impl LedgerIntf) {
85        let zkapp = zkapp.unwrap_or(true);
86
87        self.0.iter().for_each(|(kp, amount)| {
88            let (_tag, mut account, loc) = ledger
89                .get_or_create(&AccountId::new(
90                    kp.public.into_compressed(),
91                    TokenId::default(),
92                ))
93                .unwrap();
94
95            use AuthRequired::Either;
96            let permissions = Permissions {
97                edit_state: Either,
98                access: AuthRequired::None,
99                send: Either,
100                receive: AuthRequired::None,
101                set_delegate: Either,
102                set_permissions: Either,
103                set_verification_key: crate::SetVerificationKey {
104                    auth: Either,
105                    txn_version: TXN_VERSION_CURRENT,
106                },
107                set_zkapp_uri: Either,
108                edit_action_state: Either,
109                set_token_symbol: Either,
110                increment_nonce: Either,
111                set_voting_for: Either,
112                set_timing: Either,
113            };
114
115            let zkapp = if zkapp {
116                let zkapp = ZkAppAccount {
117                    verification_key: Some(VerificationKeyWire::new(
118                        crate::dummy::trivial_verification_key(),
119                    )),
120                    ..Default::default()
121                };
122
123                Some(zkapp.into())
124            } else {
125                None
126            };
127
128            account.balance = Balance::from_u64(*amount);
129            account.permissions = permissions;
130            account.zkapp = zkapp;
131
132            ledger.set(&loc, account);
133        });
134    }
135
136    pub fn gen() -> Self {
137        let mut rng = rand::thread_rng();
138
139        let mut tbl = HashSet::with_capacity(256);
140
141        let init = (0..NUM_ACCOUNTS)
142            .map(|_| {
143                let kp = loop {
144                    let keypair = gen_keypair();
145                    let compressed = keypair.public.into_compressed();
146                    if !tbl.contains(&HashableCompressedPubKey(compressed)) {
147                        break keypair;
148                    }
149                };
150
151                let amount = rng.gen_range(MIN_INIT_BALANCE..MAX_INIT_BALANCE);
152                tbl.insert(HashableCompressedPubKey(kp.public.into_compressed()));
153                (kp, amount)
154            })
155            .collect();
156
157        Self(init)
158    }
159}
160
161impl TransactionSpec {
162    pub fn gen(init_ledger: &InitLedger, nonces: &mut HashMap<HashableKeypair, Nonce>) -> Self {
163        let mut rng = rand::thread_rng();
164
165        let pk = |(kp, _): (Keypair, u64)| kp.public.into_compressed();
166
167        let receiver_is_new: bool = rng.gen();
168
169        let mut gen_index = || rng.gen_range(0..init_ledger.0.len().checked_sub(1).unwrap());
170
171        let receiver_index = if receiver_is_new {
172            None
173        } else {
174            Some(gen_index())
175        };
176
177        let receiver = match receiver_index {
178            None => gen_keypair().public.into_compressed(),
179            Some(i) => pk(init_ledger.0[i].clone()),
180        };
181
182        let sender = {
183            let i = match receiver_index {
184                None => gen_index(),
185                Some(j) => loop {
186                    let i = gen_index();
187                    if i != j {
188                        break i;
189                    }
190                },
191            };
192            init_ledger.0[i].0.clone()
193        };
194
195        let nonce = nonces
196            .get(&HashableKeypair(sender.clone()))
197            .cloned()
198            .unwrap();
199
200        let amount = Amount::from_u64(rng.gen_range(1_000_000..100_000_000));
201        let fee = Fee::from_u64(rng.gen_range(1_000_000..100_000_000));
202
203        let old = nonces.get_mut(&HashableKeypair(sender.clone())).unwrap();
204        *old = old.incr();
205
206        Self {
207            fee,
208            sender: (sender, nonce),
209            receiver,
210            amount,
211        }
212    }
213}
214
215impl TestSpec {
216    fn mk_gen(num_transactions: Option<u64>) -> TestSpec {
217        let num_transactions = num_transactions.unwrap_or(NUM_TRANSACTIONS);
218
219        let init_ledger = InitLedger::gen();
220
221        let mut map = init_ledger
222            .0
223            .iter()
224            .map(|(kp, _)| (HashableKeypair(kp.clone()), Nonce::zero()))
225            .collect();
226
227        let specs = (0..num_transactions)
228            .map(|_| TransactionSpec::gen(&init_ledger, &mut map))
229            .collect();
230
231        Self { init_ledger, specs }
232    }
233
234    pub fn gen() -> Self {
235        Self::mk_gen(Some(NUM_TRANSACTIONS))
236    }
237}
238
239#[derive(Debug)]
240pub struct UpdateStatesSpec {
241    pub fee: Fee,
242    pub sender: (Keypair, Nonce),
243    pub fee_payer: Option<(Keypair, Nonce)>,
244    pub receivers: Vec<(CompressedPubKey, Amount)>,
245    pub amount: Amount,
246    pub zkapp_account_keypairs: Vec<Keypair>,
247    pub memo: Memo,
248    pub new_zkapp_account: bool,
249    pub snapp_update: zkapp_command::Update,
250    // Authorization for the update being performed
251    pub current_auth: AuthRequired,
252    pub actions: Vec<Vec<Fp>>,
253    pub events: Vec<Vec<Fp>>,
254    pub call_data: Fp,
255    pub preconditions: Option<zkapp_command::Preconditions>,
256}
257
258pub fn trivial_zkapp_account(
259    permissions: Option<Permissions<AuthRequired>>,
260    vk: VerificationKey,
261    pk: CompressedPubKey,
262) -> Account {
263    let id = AccountId::new(pk, TokenId::default());
264    let mut account = Account::create_with(id, Balance::from_u64(1_000_000_000_000_000));
265    account.permissions = permissions.unwrap_or_else(Permissions::user_default);
266    account.zkapp = Some(
267        ZkAppAccount {
268            verification_key: Some(VerificationKeyWire::new(vk)),
269            ..Default::default()
270        }
271        .into(),
272    );
273    account
274}
275
276pub fn create_trivial_zkapp_account(
277    permissions: Option<Permissions<AuthRequired>>,
278    vk: VerificationKey,
279    ledger: &mut Mask,
280    pk: CompressedPubKey,
281) {
282    let id = AccountId::new(pk.clone(), TokenId::default());
283    let account = trivial_zkapp_account(permissions, vk, pk);
284    assert!(BaseLedger::location_of_account(ledger, &id).is_none());
285    ledger.get_or_create_account(id, account).unwrap();
286}