Skip to content

Commit 859dcf2

Browse files
authored
Merge pull request #1527 from o1-labs/dw/move-for-tests-transaction-logic
Ledger/tx-logic: move for_tests into a submodule
2 parents 7f86d88 + 565d710 commit 859dcf2

File tree

3 files changed

+296
-288
lines changed

3 files changed

+296
-288
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6666
([#1497](https://github.com/o1-labs/mina-rust/pull/1497))
6767
- **ledger/scan_state/transaction_logic**: update OCaml references in `mod.rs`
6868
([#1525](https://github.com/o1-labs/mina-rust/pull/1525))
69+
- **ledger/scan_state/transaction_logic**: move submodule `for_tests` into a
70+
new file `zkapp_command/for_tests.rs`
71+
([#1527](https://github.com/o1-labs/mina-rust/pull/1527)).
6972

7073
## v0.17.0
7174

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

0 commit comments

Comments
 (0)