Skip to main content

miden_client/store/
account.rs

1// ACCOUNT RECORD
2// ================================================================================================
3use alloc::vec::Vec;
4use core::fmt::Display;
5
6use miden_protocol::account::{Account, AccountId, PartialAccount};
7use miden_protocol::{Felt, Word};
8
9use crate::ClientError;
10
11// ACCOUNT RECORD DATA
12// ================================================================================================
13
14/// Represents types of records retrieved from the store
15#[derive(Debug)]
16pub enum AccountRecordData {
17    Full(Account),
18    Partial(PartialAccount),
19}
20
21impl AccountRecordData {
22    pub fn nonce(&self) -> Felt {
23        match self {
24            AccountRecordData::Full(account) => account.nonce(),
25            AccountRecordData::Partial(partial_account) => partial_account.nonce(),
26        }
27    }
28}
29
30// ACCOUNT RECORD
31// ================================================================================================
32
33/// Represents a stored account state along with its status.
34///
35/// The account should be stored in the database with its parts normalized. Meaning that the
36/// account header, vault, storage and code are stored separately. This is done to avoid data
37/// duplication as the header can reference the same elements if they have equal roots.
38#[derive(Debug)]
39pub struct AccountRecord {
40    /// Full account object.
41    account_data: AccountRecordData,
42    /// Status of the tracked account.
43    status: AccountStatus,
44}
45
46impl AccountRecord {
47    pub fn new(account_data: AccountRecordData, status: AccountStatus) -> Self {
48        // TODO: remove this?
49        #[cfg(debug_assertions)]
50        {
51            let account_seed = match &account_data {
52                AccountRecordData::Full(acc) => acc.seed(),
53                AccountRecordData::Partial(acc) => acc.seed(),
54            };
55            debug_assert_eq!(account_seed, status.seed().copied(), "account seed mismatch");
56        }
57
58        Self { account_data, status }
59    }
60
61    pub fn is_locked(&self) -> bool {
62        self.status.is_locked()
63    }
64
65    pub fn nonce(&self) -> Felt {
66        self.account_data.nonce()
67    }
68}
69
70impl TryFrom<AccountRecord> for Account {
71    type Error = ClientError;
72
73    fn try_from(value: AccountRecord) -> Result<Self, Self::Error> {
74        match value.account_data {
75            AccountRecordData::Full(acc) => Ok(acc),
76            AccountRecordData::Partial(acc) => Err(ClientError::AccountRecordNotFull(acc.id())),
77        }
78    }
79}
80
81impl TryFrom<AccountRecord> for PartialAccount {
82    type Error = ClientError;
83
84    fn try_from(value: AccountRecord) -> Result<Self, Self::Error> {
85        match value.account_data {
86            AccountRecordData::Partial(acc) => Ok(acc),
87            AccountRecordData::Full(acc) => Err(ClientError::AccountRecordNotPartial(acc.id())),
88        }
89    }
90}
91
92// ACCOUNT STATUS
93// ================================================================================================
94
95/// Represents the status of an account tracked by the client.
96///
97/// The status of an account may change by local or external factors.
98#[derive(Debug, Clone)]
99pub enum AccountStatus {
100    /// The account is new and hasn't been used yet. The seed used to create the account is
101    /// stored in this state.
102    New { seed: Word },
103    /// The account is tracked by the node and was used at least once.
104    Tracked,
105    /// The local account state doesn't match the node's state, rendering it unusable.
106    /// Only used for private accounts.
107    /// The seed is preserved for private accounts with nonce=0 that need reconstruction via
108    /// `Account::new()`.
109    Locked { seed: Option<Word> },
110}
111
112impl AccountStatus {
113    pub fn is_new(&self) -> bool {
114        matches!(self, AccountStatus::New { .. })
115    }
116
117    pub fn is_locked(&self) -> bool {
118        matches!(self, AccountStatus::Locked { .. })
119    }
120
121    pub fn seed(&self) -> Option<&Word> {
122        match self {
123            AccountStatus::New { seed } => Some(seed),
124            AccountStatus::Locked { seed } => seed.as_ref(),
125            AccountStatus::Tracked => None,
126        }
127    }
128}
129
130impl Display for AccountStatus {
131    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
132        match self {
133            AccountStatus::New { .. } => write!(f, "New"),
134            AccountStatus::Tracked => write!(f, "Tracked"),
135            AccountStatus::Locked { .. } => write!(f, "Locked"),
136        }
137    }
138}
139
140// ACCOUNT UPDATES
141// ================================================================================================
142
143/// Contains account changes to apply to the store.
144pub struct AccountUpdates {
145    /// Updated public accounts.
146    updated_public_accounts: Vec<Account>,
147    /// Network account commitments that don't match the current tracked state for private
148    /// accounts.
149    mismatched_private_accounts: Vec<(AccountId, Word)>,
150}
151
152impl AccountUpdates {
153    /// Creates a new instance of `AccountUpdates`.
154    pub fn new(
155        updated_public_accounts: Vec<Account>,
156        mismatched_private_accounts: Vec<(AccountId, Word)>,
157    ) -> Self {
158        Self {
159            updated_public_accounts,
160            mismatched_private_accounts,
161        }
162    }
163
164    /// Returns the updated public accounts.
165    pub fn updated_public_accounts(&self) -> &[Account] {
166        &self.updated_public_accounts
167    }
168
169    /// Returns the mismatched private accounts.
170    pub fn mismatched_private_accounts(&self) -> &[(AccountId, Word)] {
171        &self.mismatched_private_accounts
172    }
173}