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, AccountCode, AccountId, PartialAccount};
7use miden_protocol::address::Address;
8use miden_protocol::{Felt, Word};
9
10use crate::ClientError;
11
12// ACCOUNT RECORD DATA
13// ================================================================================================
14
15/// Represents types of records retrieved from the store
16#[derive(Debug)]
17pub enum AccountRecordData {
18    Full(Account),
19    Partial(PartialAccount),
20}
21
22impl AccountRecordData {
23    pub fn id(&self) -> AccountId {
24        match self {
25            AccountRecordData::Full(account) => account.id(),
26            AccountRecordData::Partial(partial_account) => partial_account.id(),
27        }
28    }
29
30    pub fn commitment(&self) -> Word {
31        match self {
32            AccountRecordData::Full(account) => account.commitment(),
33            AccountRecordData::Partial(partial_account) => partial_account.commitment(),
34        }
35    }
36
37    pub fn initial_commitment(&self) -> Word {
38        match self {
39            AccountRecordData::Full(account) => account.initial_commitment(),
40            AccountRecordData::Partial(partial_account) => partial_account.initial_commitment(),
41        }
42    }
43
44    pub fn nonce(&self) -> Felt {
45        match self {
46            AccountRecordData::Full(account) => account.nonce(),
47            AccountRecordData::Partial(partial_account) => partial_account.nonce(),
48        }
49    }
50
51    pub fn seed(&self) -> Option<Word> {
52        match self {
53            AccountRecordData::Full(account) => account.seed(),
54            AccountRecordData::Partial(partial_account) => partial_account.seed(),
55        }
56    }
57
58    pub fn is_new(&self) -> bool {
59        match self {
60            AccountRecordData::Full(account) => account.is_new(),
61            AccountRecordData::Partial(partial_account) => partial_account.is_new(),
62        }
63    }
64
65    pub fn has_public_state(&self) -> bool {
66        match self {
67            AccountRecordData::Full(account) => account.has_public_state(),
68            AccountRecordData::Partial(partial_account) => partial_account.has_public_state(),
69        }
70    }
71
72    pub fn code(&self) -> &AccountCode {
73        match self {
74            AccountRecordData::Full(account) => account.code(),
75            AccountRecordData::Partial(partial_account) => partial_account.code(),
76        }
77    }
78}
79
80// ACCOUNT RECORD
81// ================================================================================================
82
83/// Represents a stored account state along with its status.
84///
85/// The account should be stored in the database with its parts normalized. Meaning that the
86/// account header, vault, storage and code are stored separately. This is done to avoid data
87/// duplication as the header can reference the same elements if they have equal roots.
88#[derive(Debug)]
89pub struct AccountRecord {
90    /// Full account object.
91    account_data: AccountRecordData,
92    /// Status of the tracked account.
93    status: AccountStatus,
94    /// Addresses by which this account can be referenced.
95    addresses: Vec<Address>,
96}
97
98impl AccountRecord {
99    pub fn new(
100        account_data: AccountRecordData,
101        status: AccountStatus,
102        addresses: Vec<Address>,
103    ) -> Self {
104        // TODO: remove this?
105        #[cfg(debug_assertions)]
106        {
107            let account_seed = match &account_data {
108                AccountRecordData::Full(acc) => acc.seed(),
109                AccountRecordData::Partial(acc) => acc.seed(),
110            };
111            debug_assert_eq!(account_seed, status.seed().copied(), "account seed mismatch");
112        }
113
114        Self { account_data, status, addresses }
115    }
116
117    pub fn id(&self) -> AccountId {
118        match &self.account_data {
119            AccountRecordData::Full(acc) => acc.id(),
120            AccountRecordData::Partial(acc) => acc.id(),
121        }
122    }
123
124    pub fn account_data(&self) -> &AccountRecordData {
125        &self.account_data
126    }
127
128    pub fn status(&self) -> &AccountStatus {
129        &self.status
130    }
131
132    pub fn is_locked(&self) -> bool {
133        self.status.is_locked()
134    }
135
136    pub fn seed(&self) -> Option<Word> {
137        match &self.account_data {
138            AccountRecordData::Full(acc) => acc.seed(),
139            AccountRecordData::Partial(acc) => acc.seed(),
140        }
141    }
142
143    pub fn nonce(&self) -> Felt {
144        match &self.account_data {
145            AccountRecordData::Full(acc) => acc.nonce(),
146            AccountRecordData::Partial(acc) => acc.nonce(),
147        }
148    }
149
150    pub fn commitment(&self) -> Word {
151        match &self.account_data {
152            AccountRecordData::Full(acc) => acc.commitment(),
153            AccountRecordData::Partial(acc) => acc.commitment(),
154        }
155    }
156
157    pub fn addresses(&self) -> &Vec<Address> {
158        &self.addresses
159    }
160
161    pub fn code(&self) -> AccountCode {
162        match &self.account_data {
163            AccountRecordData::Full(acc) => acc.code().clone(),
164            AccountRecordData::Partial(acc) => acc.code().clone(),
165        }
166    }
167}
168
169impl TryFrom<AccountRecord> for Account {
170    type Error = ClientError;
171
172    fn try_from(value: AccountRecord) -> Result<Self, Self::Error> {
173        match value.account_data {
174            AccountRecordData::Full(acc) => Ok(acc),
175            AccountRecordData::Partial(acc) => Err(ClientError::AccountRecordNotFull(acc.id())),
176        }
177    }
178}
179
180impl TryFrom<AccountRecord> for PartialAccount {
181    type Error = ClientError;
182
183    fn try_from(value: AccountRecord) -> Result<Self, Self::Error> {
184        match value.account_data {
185            AccountRecordData::Partial(acc) => Ok(acc),
186            AccountRecordData::Full(acc) => Err(ClientError::AccountRecordNotPartial(acc.id())),
187        }
188    }
189}
190
191// ACCOUNT STATUS
192// ================================================================================================
193
194/// Represents the status of an account tracked by the client.
195///
196/// The status of an account may change by local or external factors.
197#[derive(Debug)]
198pub enum AccountStatus {
199    /// The account is new and hasn't been used yet. The seed used to create the account is
200    /// stored in this state.
201    New { seed: Word },
202    /// The account is tracked by the node and was used at least once.
203    Tracked,
204    /// The local account state doesn't match the node's state, rendering it unusable.
205    /// Only used for private accounts.
206    /// The seed is preserved for private accounts with nonce=0 that need reconstruction via
207    /// `Account::new()`.
208    Locked { seed: Option<Word> },
209}
210
211impl AccountStatus {
212    pub fn is_locked(&self) -> bool {
213        matches!(self, AccountStatus::Locked { .. })
214    }
215
216    pub fn seed(&self) -> Option<&Word> {
217        match self {
218            AccountStatus::New { seed } => Some(seed),
219            AccountStatus::Locked { seed } => seed.as_ref(),
220            AccountStatus::Tracked => None,
221        }
222    }
223}
224
225impl Display for AccountStatus {
226    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
227        match self {
228            AccountStatus::New { .. } => write!(f, "New"),
229            AccountStatus::Tracked => write!(f, "Tracked"),
230            AccountStatus::Locked { .. } => write!(f, "Locked"),
231        }
232    }
233}
234
235// ACCOUNT UPDATES
236// ================================================================================================
237
238/// Contains account changes to apply to the store.
239pub struct AccountUpdates {
240    /// Updated public accounts.
241    updated_public_accounts: Vec<Account>,
242    /// Network account commitments that don't match the current tracked state for private
243    /// accounts.
244    mismatched_private_accounts: Vec<(AccountId, Word)>,
245}
246
247impl AccountUpdates {
248    /// Creates a new instance of `AccountUpdates`.
249    pub fn new(
250        updated_public_accounts: Vec<Account>,
251        mismatched_private_accounts: Vec<(AccountId, Word)>,
252    ) -> Self {
253        Self {
254            updated_public_accounts,
255            mismatched_private_accounts,
256        }
257    }
258
259    /// Returns the updated public accounts.
260    pub fn updated_public_accounts(&self) -> &[Account] {
261        &self.updated_public_accounts
262    }
263
264    /// Returns the mismatched private accounts.
265    pub fn mismatched_private_accounts(&self) -> &[(AccountId, Word)] {
266        &self.mismatched_private_accounts
267    }
268}