1use core::str::FromStr;
2use mina_curves::pasta::Fp;
3use mina_p2p_messages::binprot::BinProtWrite;
4use multihash::{Blake2b256, Hasher};
5use serde::{Deserialize, Deserializer, Serialize};
6use serde_json::Value;
7use std::fmt::{self, Display, Formatter};
8
9use ledger::{
10 scan_state::currency::{Amount, Balance, Magnitude, Nonce, Slot, SlotSpan, TxnVersion},
11 AuthRequired, Permissions, ReceiptChainHash, SetVerificationKey, Timing, TokenId, TokenSymbol,
12 VotingFor, ZkAppAccount, ZkAppUri,
13};
14use openmina_node_account::{AccountPublicKey, AccountSecretKey};
15
16use crate::ledger::LEDGER_DEPTH;
17
18type RawCurrency = String;
19type RawSlot = String;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Ledger {
23 pub accounts: Option<Vec<Account>>,
24 pub num_accounts: Option<usize>,
25 pub balances: Option<Vec<(usize, RawCurrency)>>,
26 pub hash: Option<String>,
27 pub s3_data_hash: Option<String>,
28 pub name: Option<String>,
29 pub add_genesis_winner: Option<bool>,
30}
31
32impl Ledger {
33 pub fn find_account(&self, pk: &AccountPublicKey) -> Option<Account> {
34 let pk_str = pk.to_string();
35 self.accounts.as_ref().and_then(|accounts| {
36 accounts
37 .iter()
38 .find(|account| account.pk == pk_str)
39 .cloned()
40 })
41 }
42
43 pub fn accounts_with_genesis_winner(&self) -> Vec<Account> {
46 let mut accounts = match self.accounts {
47 Some(ref accounts) => accounts.clone(),
48 None => Vec::with_capacity(1),
49 };
50 if !self.add_genesis_winner.unwrap_or(true) {
51 return accounts;
52 }
53 let genesis_winner_pk =
54 "B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg".to_string();
55 match accounts.first() {
56 Some(first) if first.pk == genesis_winner_pk => accounts,
57 _ => {
58 let genesis_winner = Account::new(
59 genesis_winner_pk.clone(),
60 "0.000001000".to_string(),
61 Some(genesis_winner_pk),
62 );
63 accounts.insert(0, genesis_winner);
64 accounts
65 }
66 }
67 }
68
69 pub fn ledger_name(&self) -> String {
77 self.hash.clone().unwrap_or_else(|| {
78 build_ledger_name(
79 self.num_accounts.unwrap_or(0),
80 self.balances
81 .clone()
82 .unwrap_or_default()
83 .iter()
84 .map(Clone::clone),
85 )
86 })
87 }
88}
89
90pub fn build_ledger_name(
91 num_accounts: usize,
92 balances: impl Iterator<Item = (usize, RawCurrency)>,
93) -> String {
94 let mut hash = Blake2b256::default();
95 hash.update(LEDGER_DEPTH.to_string().as_bytes());
96 hash.update(num_accounts.to_string().as_bytes());
97 let balances = balances.fold(String::new(), |acc, (i, balance)| {
98 format!("{} {} {}", acc, i, balance)
99 });
100 hash.update(balances.as_bytes());
101 let empty_account = ledger::Account::empty();
102 hash.update(empty_account.hash().to_string().as_bytes());
103 let mut empty_account_enc: Vec<u8> = Vec::new();
104 empty_account
105 .binprot_write(&mut empty_account_enc)
106 .expect("failed to write account");
107 hash.update(empty_account_enc.as_slice());
108 format!("{:x?}", hash.finalize())
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct Account {
113 pk: String,
114 sk: Option<String>,
115 pub(super) balance: RawCurrency,
116 delegate: Option<String>,
117 token_id: Option<String>,
118 token_symbol: Option<Vec<u8>>,
119 #[serde(default, deserialize_with = "string_or_u32_option")]
120 nonce: Option<u32>,
121 receipt_chain_hash: Option<String>,
122 voting_for: Option<String>,
123 timing: Option<AccountTiming>,
124 permissions: Option<AccountPermissions>,
125 zkapp: Option<Zkapp>,
126}
127
128impl Account {
129 pub fn new(pk: String, balance: RawCurrency, delegate: Option<String>) -> Account {
130 Account {
131 pk,
132 sk: None,
133 balance,
134 delegate,
135 token_id: None,
136 token_symbol: None,
137 nonce: None,
138 receipt_chain_hash: None,
139 voting_for: None,
140 timing: None,
141 permissions: None,
142 zkapp: None,
143 }
144 }
145}
146
147impl Account {
148 pub fn public_key(&self) -> Result<AccountPublicKey, AccountConfigError> {
149 let cpk = ledger::compressed_pubkey_from_address_maybe_with_error(&self.pk)
150 .map_err(|_| AccountConfigError::MalformedKey(self.pk.clone()))?;
151 Ok(AccountPublicKey::from(cpk))
152 }
153
154 pub fn balance(&self) -> Balance {
155 Balance::of_mina_string_exn(&self.balance)
156 }
157
158 pub fn delegate(&self) -> Result<Option<AccountPublicKey>, AccountConfigError> {
159 let is_default_token = self.token_id()?.is_default();
160 match self.delegate.as_ref() {
161 Some(delegate) if is_default_token => {
162 let cpk = ledger::compressed_pubkey_from_address_maybe_with_error(delegate)
163 .map_err(|_| AccountConfigError::MalformedKey(delegate.clone()))?;
164 Ok(Some(AccountPublicKey::from(cpk)))
165 }
166 Some(_) => Err(AccountConfigError::DelegateSetOnNonDefaultTokenAccount),
167 None => {
168 if is_default_token {
169 self.public_key().map(Option::Some)
170 } else {
171 Ok(None)
172 }
173 }
174 }
175 }
176
177 pub fn secret_key(&self) -> Result<Option<AccountSecretKey>, AccountConfigError> {
178 match self.sk.as_ref() {
179 Some(sk) => AccountSecretKey::from_str(sk)
180 .map(Some)
181 .map_err(|_| AccountConfigError::MalformedKey(sk.to_string())),
182 None => Ok(None),
183 }
184 }
185
186 pub fn token_id(&self) -> Result<TokenId, AccountConfigError> {
187 let token_fp = self
188 .token_id
189 .as_ref()
190 .map(|id| {
191 Fp::from_str(id).map_err(|()| AccountConfigError::MalformedTokenId(id.clone()))
192 })
193 .transpose()?;
194 Ok(token_fp.map_or(TokenId::default(), TokenId))
195 }
196
197 pub fn token_symbol(&self) -> TokenSymbol {
198 self.token_symbol
199 .clone()
200 .map(TokenSymbol::from)
201 .unwrap_or_default()
202 }
203
204 pub fn nonce(&self) -> Nonce {
205 self.nonce.map_or(Nonce::zero(), Nonce::from_u32)
206 }
207
208 pub fn receipt_chain_hash(&self) -> Result<ReceiptChainHash, AccountConfigError> {
209 self.receipt_chain_hash
210 .as_ref()
211 .map_or(Ok(ReceiptChainHash::empty()), |hash| {
212 ReceiptChainHash::parse_str(hash)
213 .map_err(|_| AccountConfigError::MalformedReceiptChainHash(hash.clone()))
214 })
215 }
216
217 pub fn voting_for(&self) -> Result<VotingFor, AccountConfigError> {
218 self.voting_for
219 .as_ref()
220 .map_or(Ok(VotingFor::dummy()), |hash| {
221 VotingFor::parse_str(hash)
222 .map_err(|_| AccountConfigError::MalformedVotingFor(hash.clone()))
223 })
224 }
225
226 pub fn timing(&self) -> Result<Timing, AccountConfigError> {
227 self.timing
228 .as_ref()
229 .map_or(Ok(Timing::Untimed), AccountTiming::to_timing)
230 }
231
232 pub fn permissions(&self) -> Permissions<AuthRequired> {
233 self.permissions.as_ref().map_or(
234 Permissions::user_default(),
235 AccountPermissions::to_permissions,
236 )
237 }
238
239 pub fn zkapp(&self) -> Result<Option<Box<ZkAppAccount>>, AccountConfigError> {
240 self.zkapp.as_ref().map(Zkapp::to_zkapp_account).transpose()
241 }
242
243 pub fn to_account(&self) -> Result<ledger::Account, AccountConfigError> {
244 let mut account = ledger::Account::empty();
245 account.public_key = self
246 .public_key()?
247 .try_into()
248 .map_err(|_| AccountConfigError::InvalidBigInt)?;
249 account.token_id = self.token_id()?;
250 account.token_symbol = self.token_symbol();
251 account.balance = self.balance();
252 account.nonce = self.nonce();
253 account.receipt_chain_hash = self.receipt_chain_hash()?;
254 account.delegate = match self.delegate()? {
255 Some(delegate) => Some(
256 delegate
257 .try_into()
258 .map_err(|_| AccountConfigError::InvalidBigInt)?,
259 ),
260 None => None,
261 };
262 account.voting_for = self.voting_for()?;
263 account.timing = self.timing()?;
264 account.permissions = self.permissions();
265 account.zkapp = self.zkapp()?;
266 Ok(account)
267 }
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct AccountTiming {
272 initial_minimum_balance: RawCurrency,
273 cliff_time: GlobalSlotSinceGenesis,
274 cliff_amount: RawCurrency,
275 vesting_period: GlobalSlotSpan,
276 vesting_increment: RawCurrency,
277}
278
279impl AccountTiming {
280 fn to_timing(&self) -> Result<Timing, AccountConfigError> {
281 let initial_minimum_balance = Balance::of_mina_string_exn(&self.initial_minimum_balance);
282 let GlobalSlotSinceGenesis(cliff_time) = self.cliff_time;
283 let cliff_amount = Amount::of_mina_string_exn(&self.cliff_amount);
284 let GlobalSlotSpan(vesting_period) = self.vesting_period;
285 let vesting_increment = Amount::of_mina_string_exn(&self.vesting_increment);
286 Ok(Timing::Timed {
287 initial_minimum_balance,
288 cliff_time: Slot::from_u32(cliff_time),
289 cliff_amount,
290 vesting_period: SlotSpan::from_u32(vesting_period),
291 vesting_increment,
292 })
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
297struct SetVrfKeyPerm {
298 auth: AuthRequired,
299 #[serde(deserialize_with = "string_or_u32")]
300 txn_version: u32,
301}
302
303#[derive(Deserialize)]
304#[serde(untagged)]
305enum StringOrU32 {
306 String(String),
307 U32(u32),
308}
309
310fn string_or_u32<'de, D>(deserializer: D) -> Result<u32, D::Error>
311where
312 D: Deserializer<'de>,
313{
314 match StringOrU32::deserialize(deserializer)? {
315 StringOrU32::String(s) => s.parse::<u32>().map_err(serde::de::Error::custom),
316 StringOrU32::U32(u) => Ok(u),
317 }
318}
319
320fn string_or_u32_option<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
321where
322 D: Deserializer<'de>,
323{
324 let opt: Option<StringOrU32> = Option::deserialize(deserializer)?;
325 match opt {
326 Some(StringOrU32::String(s)) => {
327 s.parse::<u32>().map(Some).map_err(serde::de::Error::custom)
328 }
329 Some(StringOrU32::U32(u)) => Ok(Some(u)),
330 None => Ok(None),
331 }
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct AccountPermissions {
336 access: Option<AuthRequired>,
337 edit_state: Option<AuthRequired>,
338 send: Option<AuthRequired>,
339 receive: Option<AuthRequired>,
340 set_delegate: Option<AuthRequired>,
341 set_permissions: Option<AuthRequired>,
342 set_verification_key: SetVrfKeyPerm,
344 set_zkapp_uri: Option<AuthRequired>,
345 edit_action_state: Option<AuthRequired>,
346 set_token_symbol: Option<AuthRequired>,
347 increment_nonce: Option<AuthRequired>,
348 set_voting_for: Option<AuthRequired>,
349 set_timing: Option<AuthRequired>,
350}
351
352impl AccountPermissions {
353 fn to_permissions(&self) -> Permissions<AuthRequired> {
354 Permissions {
356 access: self.access.unwrap_or(AuthRequired::None),
357 edit_state: self.edit_state.unwrap_or(AuthRequired::Signature),
358 send: self.send.unwrap_or(AuthRequired::Signature),
359 receive: self.receive.unwrap_or(AuthRequired::None),
360 set_delegate: self.set_delegate.unwrap_or(AuthRequired::Signature),
361 set_permissions: self.set_permissions.unwrap_or(AuthRequired::Signature),
362 set_verification_key: SetVerificationKey {
363 auth: self.set_verification_key.auth,
364 txn_version: TxnVersion::from_u32(self.set_verification_key.txn_version),
365 },
366 set_zkapp_uri: self.set_zkapp_uri.unwrap_or(AuthRequired::Signature),
367 edit_action_state: self.edit_action_state.unwrap_or(AuthRequired::Signature),
368 set_token_symbol: self.set_token_symbol.unwrap_or(AuthRequired::Signature),
369 increment_nonce: self.increment_nonce.unwrap_or(AuthRequired::Signature),
370 set_voting_for: self.set_voting_for.unwrap_or(AuthRequired::Signature),
371 set_timing: self.set_timing.unwrap_or(AuthRequired::Signature),
372 }
373 }
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct Zkapp {
378 app_state: Vec<String>,
379 verification_key: Option<()>,
384 zkapp_version: u32,
385 action_state: Vec<String>,
386 last_action_slot: RawSlot,
387 proved_state: bool,
388 zkapp_uri: Vec<u8>,
389}
390
391fn parse_fp(str: &str) -> Result<Fp, AccountConfigError> {
392 Fp::from_str(str).map_err(|_| AccountConfigError::MalformedFp(str.to_owned()))
393}
394
395impl Zkapp {
396 fn to_zkapp_account(&self) -> Result<Box<ZkAppAccount>, AccountConfigError> {
397 let app_state_fps: Vec<Fp> = self
398 .app_state
399 .iter()
400 .map(|fp| parse_fp(fp))
401 .collect::<Result<Vec<Fp>, AccountConfigError>>()?;
402 let app_state = if app_state_fps.len() <= 8 {
403 let mut app_state = [0.into(); 8];
404 app_state.copy_from_slice(&app_state_fps);
405 app_state
406 } else {
407 return Err(AccountConfigError::ZkAppStateTooLong(
408 self.app_state.clone(),
409 ));
410 };
411 let act_state_fps: Vec<Fp> = self
412 .action_state
413 .iter()
414 .map(|fp| parse_fp(fp))
415 .collect::<Result<Vec<Fp>, AccountConfigError>>()?;
416 let action_state = if act_state_fps.len() <= 5 {
417 let mut action_state = [0.into(); 5];
418 action_state.copy_from_slice(&act_state_fps);
419 action_state
420 } else {
421 return Err(AccountConfigError::ZkAppStateTooLong(
422 self.action_state.clone(),
423 ));
424 };
425 let last_action_slot = self
426 .last_action_slot
427 .parse::<u32>()
428 .map(Slot::from_u32)
429 .map_err(|_| AccountConfigError::MalformedSlot(self.last_action_slot.clone()))?;
430 if self.verification_key.is_some() {
431 return Err(AccountConfigError::VerificationKeyParsingNotSupported);
432 };
433 Ok(ZkAppAccount {
434 app_state,
435 verification_key: None,
436 zkapp_version: self.zkapp_version,
437 action_state,
438 last_action_slot,
439 proved_state: self.proved_state,
440 zkapp_uri: ZkAppUri::from(self.zkapp_uri.clone()),
441 }
442 .into())
443 }
444}
445
446#[derive(Debug, Clone)]
447pub enum AccountConfigError {
448 MalformedCurrencyValue(String),
449 MalformedKey(String),
450 MalformedTokenId(String),
451 MalformedReceiptChainHash(String),
452 MalformedVotingFor(String),
453 MalformedSlot(String),
454 MalformedFp(String),
455 ZkAppStateTooLong(Vec<String>),
456 VerificationKeyParsingNotSupported,
457 DelegateSetOnNonDefaultTokenAccount,
458 InvalidBigInt,
459}
460
461#[derive(Debug, Clone, Serialize)]
462struct GlobalSlotSinceGenesis(u32);
463
464impl<'de> Deserialize<'de> for GlobalSlotSinceGenesis {
465 fn deserialize<D>(deserializer: D) -> Result<GlobalSlotSinceGenesis, D::Error>
466 where
467 D: Deserializer<'de>,
468 {
469 let err = "Global slot since genesis must consist of a tag and a number";
470 let value = Value::deserialize(deserializer)?;
471 match value {
472 Value::Number(n) => {
473 let slot = n.as_u64().ok_or(serde::de::Error::custom(err))? as u32;
474 Ok(GlobalSlotSinceGenesis(slot))
475 }
476 Value::String(s) => {
477 let slot = s
478 .parse::<u32>()
479 .map_err(|_| serde::de::Error::custom(err))?;
480 Ok(GlobalSlotSinceGenesis(slot))
481 }
482 Value::Array(arr) => {
483 let tag = arr
484 .first()
485 .and_then(Value::as_str)
486 .ok_or(serde::de::Error::custom(err))?;
487 if tag != "Since_genesis" {
488 return Err(serde::de::Error::custom(err));
489 };
490 let s = arr
491 .get(1)
492 .and_then(Value::as_str)
493 .ok_or(serde::de::Error::custom(err))?;
494 let slot = s
495 .parse::<u32>()
496 .map_err(|_| serde::de::Error::custom(err))?;
497 Ok(GlobalSlotSinceGenesis(slot))
498 }
499 _ => Err(serde::de::Error::custom(err)),
500 }
501 }
502}
503
504#[derive(Debug, Clone, Serialize)]
505struct GlobalSlotSpan(u32);
506
507impl<'de> Deserialize<'de> for GlobalSlotSpan {
508 fn deserialize<D>(deserializer: D) -> Result<GlobalSlotSpan, D::Error>
509 where
510 D: Deserializer<'de>,
511 {
512 let err = "Global slot span must consist of a tag and a number";
513 let value = Value::deserialize(deserializer)?;
514 match value {
515 Value::Number(n) => {
516 let slot = n.as_u64().ok_or(serde::de::Error::custom(err))? as u32;
517 Ok(GlobalSlotSpan(slot))
518 }
519 Value::String(s) => {
520 let slot = s
521 .parse::<u32>()
522 .map_err(|_| serde::de::Error::custom(err))?;
523 Ok(GlobalSlotSpan(slot))
524 }
525 Value::Array(arr) => {
526 let tag = arr
527 .first()
528 .and_then(Value::as_str)
529 .ok_or(serde::de::Error::custom(err))?;
530 if tag != "Global_slot_span" {
531 return Err(serde::de::Error::custom(err));
532 };
533 let s = arr
534 .get(1)
535 .and_then(Value::as_str)
536 .ok_or(serde::de::Error::custom(err))?;
537 let slot = s
538 .parse::<u32>()
539 .map_err(|_| serde::de::Error::custom(err))?;
540 Ok(GlobalSlotSpan(slot))
541 }
542 _ => Err(serde::de::Error::custom(err)),
543 }
544 }
545}
546
547impl Display for AccountConfigError {
548 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
549 write!(
550 f,
551 "Account configuration error encountered in JSON config: "
552 )?;
553 match self {
554 Self::MalformedCurrencyValue(c) => write!(f, "malformed currency value ('{}')", c),
555 Self::MalformedKey(k) => write!(f, "malformed key ('{}')", k),
556 Self::MalformedTokenId(tid) => write!(f, "malformed token id ('{}')", tid),
557 Self::MalformedReceiptChainHash(rch) => {
558 write!(f, "malformed receipt chain hash ('{}')", rch)
559 }
560 Self::MalformedVotingFor(vf) => write!(f, "malformed voting_for ('{}')", vf),
561 Self::MalformedSlot(s) => write!(f, "malformed slot ('{}')", s),
562 Self::MalformedFp(fp) => write!(f, "malformed field value ('{}')", fp),
563 Self::ZkAppStateTooLong(app_state) => {
564 write!(f, "zkapp app state too long ('{:?}')", app_state)
565 }
566 Self::VerificationKeyParsingNotSupported => {
567 write!(f, "verification key parsing not supported yet!")
568 }
569 Self::DelegateSetOnNonDefaultTokenAccount => {
570 write!(f, "delegate set on non-default token account")
571 }
572 Self::InvalidBigInt => {
573 write!(f, "Invalid BigInt")
574 }
575 }
576 }
577}
578
579impl std::error::Error for AccountConfigError {}