Skip to content

Commit d6b4773

Browse files
committed
Ledger/tx-logic: move for_tests into a submodule
1 parent 53afdd1 commit d6b4773

File tree

2 files changed

+299
-288
lines changed

2 files changed

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

0 commit comments

Comments
 (0)