node/daemon_json/
json_ledger.rs

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    // Add the genesis winner account if the config says it should be added and also
44    // if its not already present at the head of the accounts list.
45    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    /// Typically a ledger is identified by its hash, but if hash is
70    /// not given in config, we construct a name by hashing the ledger
71    /// config so that we can recognise a ledger we already have
72    /// cached without reconstructing it (which is expensive).
73    /// Note that the name contains some constants - the purpose of this
74    /// is to invalidate all caches when these constants change.
75    /// See: <https://github.com/MinaProtocol/mina/blob/develop/src/lib/genesis_ledger_helper/genesis_ledger_helper.ml#L105>
76    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    // TODO: make optional too
343    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        // Defaults from https://github.com/MinaProtocol/mina/blob/3.0.0devnet/src/lib/mina_base/permissions.ml#L580-L594
355        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    // These verification keys currently don't appear in configs, but
380    // they could in the future. We ignore them for now, but should they
381    // appear, serialization is likely to crash and we'll have to update
382    // this type and relevant conversion functions.
383    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 {}