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