miden_node_rpc/server/
admission.rs1use std::sync::Arc;
2
3use miden_node_store::DatabaseError;
4use miden_node_store::allowlist::{AccountAllowlist, InvitationCode, InvitationStatus};
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#[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 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 pub(crate) async fn is_invitation_code_valid(
60 &self,
61 invitation_code: InvitationCode,
62 ) -> Result<bool, DatabaseError> {
63 if self.disabled {
64 return Ok(true);
65 }
66 let status = self.allowlist.invitation_status(invitation_code).await?;
67 Ok(status == InvitationStatus::Unused)
68 }
69
70 #[miden_instrument(
72 target = COMPONENT,
73 name = "account_admission.check",
74 fields(account.id = update.account_id()),
75 err,
76 )]
77 pub(crate) async fn check(&self, update: &TxAccountUpdate) -> tonic::Result<()> {
78 if self.disabled || !update.initial_state_commitment().is_empty() {
79 return Ok(());
80 }
81
82 if let AccountUpdateDetails::Public(patch) = update.details() {
85 let account = patch
86 .try_to_new_account()
87 .map_err(|error| Status::invalid_argument(error.to_string()))?;
88 if NetworkAccount::new(account).is_ok() {
89 return Ok(());
90 }
91 }
92
93 let account_id = update.account_id();
94 let allowed = self.is_account_allowed(account_id).await.map_err(|err| {
95 error!(err, target: LOG_TARGET, "Account allowlist lookup failed");
96 Status::internal("account allowlist lookup failed")
97 })?;
98 if !allowed {
99 return Err(Status::permission_denied(format!(
100 "account {account_id} is not registered"
101 )));
102 }
103
104 Ok(())
105 }
106}