Skip to main content

miden_node_rpc/server/
admission.rs

1use std::sync::Arc;
2
3use miden_node_store::DatabaseError;
4use miden_node_store::allowlist::AccountAllowlist;
5use miden_node_tracing::{error, miden_instrument};
6use miden_protocol::account::{AccountId, AccountUpdateDetails};
7use miden_protocol::transaction::TxAccountUpdate;
8use miden_standards::account::auth::NetworkAccount;
9use tonic::Status;
10
11use crate::{COMPONENT, FundingClient, LOG_TARGET};
12
13/// Account creation policy shared by the public and internal sequencer APIs.
14#[derive(Clone)]
15pub struct AccountAdmission {
16    pub(crate) allowlist: Arc<AccountAllowlist>,
17    pub(crate) funding: Option<FundingClient>,
18    disabled: bool,
19}
20
21impl AccountAdmission {
22    pub fn enabled(allowlist: Arc<AccountAllowlist>) -> Self {
23        Self {
24            allowlist,
25            funding: None,
26            disabled: false,
27        }
28    }
29
30    pub fn disabled(allowlist: Arc<AccountAllowlist>) -> Self {
31        Self { allowlist, funding: None, disabled: true }
32    }
33
34    pub(crate) fn is_disabled(&self) -> bool {
35        self.disabled
36    }
37
38    #[must_use]
39    pub fn with_funding_client(mut self, client: Option<FundingClient>) -> Self {
40        self.funding = client;
41        self
42    }
43
44    /// Returns true if enforcement is disabled or the account is allowlisted.
45    pub(crate) async fn is_account_allowed(
46        &self,
47        account_id: AccountId,
48    ) -> Result<bool, DatabaseError> {
49        if self.disabled {
50            return Ok(true);
51        }
52        self.allowlist.contains_account(account_id).await
53    }
54
55    /// Rejects the submission if it creates an unregistered, non-network account.
56    #[miden_instrument(
57        target = COMPONENT,
58        name = "account_admission.check",
59        fields(account.id = update.account_id()),
60        err,
61    )]
62    pub(crate) async fn check(&self, update: &TxAccountUpdate) -> tonic::Result<()> {
63        if self.disabled || !update.initial_state_commitment().is_empty() {
64            return Ok(());
65        }
66
67        // New public accounts include their full state. Use the store's network-account
68        // classification rule before the account exists on chain.
69        if let AccountUpdateDetails::Public(patch) = update.details() {
70            let account = patch
71                .try_to_new_account()
72                .map_err(|error| Status::invalid_argument(error.to_string()))?;
73            if NetworkAccount::new(account).is_ok() {
74                return Ok(());
75            }
76        }
77
78        let account_id = update.account_id();
79        let allowed = self.is_account_allowed(account_id).await.map_err(|err| {
80            error!(err, target: LOG_TARGET, "Account allowlist lookup failed");
81            Status::internal("account allowlist lookup failed")
82        })?;
83        if !allowed {
84            return Err(Status::permission_denied(format!(
85                "account {account_id} is not registered"
86            )));
87        }
88
89        Ok(())
90    }
91}