Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions bin/node/src/commands/modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ use miden_node_proto::clients::{
ValidatorClient,
WantsConnection,
};
use miden_node_rpc::{PreAuthSubmission, Rpc, RpcMode, SequencerInternal, ValidatorClients};
use miden_node_rpc::{
AccountAdmission,
PreAuthSubmission,
Rpc,
RpcMode,
SequencerInternal,
ValidatorClients,
};
use miden_node_store::allowlist::AccountAllowlist;
use miden_node_store::{BlockWriter, DataDirectory, ProofWriter, State, WriterTask};
use miden_node_tracing::info;
use miden_node_tracing::{info, warn};
use miden_node_utils::clap::duration_to_human_readable_string;
use miden_node_utils::formatting::format_endpoint;
use miden_node_utils::shutdown::CancellationToken;
Expand Down Expand Up @@ -59,6 +66,10 @@ pub struct SequencerCommand {
/// Require external authentication and network isolation.
#[arg(long = "admin.listen", env = "MIDEN_NODE_ADMIN_LISTEN", value_name = "IP:PORT")]
pub admin_listen: Option<SocketAddr>,

/// Allow unrestricted account creation. Use only on development networks.
#[arg(long, env = "MIDEN_NODE_DISABLE_ACCOUNT_ALLOWLIST")]
pub disable_account_allowlist: bool,
Comment thread
Mirko-von-Leipzig marked this conversation as resolved.
}

impl SequencerCommand {
Expand All @@ -76,6 +87,12 @@ impl SequencerCommand {
let block_prover_monitor =
remote_prover_monitor(self.block_producer.block_prover.url.as_ref())?;
let allowlist = Arc::new(self.load_allowlist()?);
let account_admission = if self.disable_account_allowlist {
warn!(target: crate::LOG_TARGET, "Account allowlist enforcement is disabled");
AccountAdmission::disabled(Arc::clone(&allowlist))
} else {
AccountAdmission::enabled(Arc::clone(&allowlist))
};
let (state, block_writer, proof_writer, writer_task) =
load_state(&runtime, shutdown.clone()).await?;
let _disk_monitor = state.spawn_disk_monitor(shutdown.clone());
Expand Down Expand Up @@ -106,7 +123,7 @@ impl SequencerCommand {
mode: RpcMode::sequencer(
block_producer.clone(),
validator_clients,
Arc::clone(&allowlist),
account_admission.clone(),
),
ntx_builder: Some(ntx_builder_client),
grpc_options: runtime.grpc_options,
Expand Down Expand Up @@ -151,6 +168,7 @@ impl SequencerCommand {
listener: bind_rpc(internal_listen).await?,
state,
block_producer,
account_admission,
grpc_options: runtime.grpc_options,
};
tasks.spawn("sequencer internal server", sequencer_internal.serve(shutdown.clone()));
Expand Down
1 change: 1 addition & 0 deletions compose/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ services:
- --ntx-builder.url=http://ntx-builder:50301
- --rpc.network-tx-auth-header-value=secret_value
environment:
MIDEN_NODE_DISABLE_ACCOUNT_ALLOWLIST: "${MIDEN_NODE_DISABLE_ACCOUNT_ALLOWLIST:-true}"
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
OTEL_RESOURCE_ATTRIBUTES: service.instance.id=sequencer
ports:
Expand Down
2 changes: 1 addition & 1 deletion crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ miden-node-tracing = { workspace = true }
miden-node-utils = { workspace = true }
miden-objects = { workspace = true }
miden-protocol = { default-features = true, workspace = true }
miden-standards = { workspace = true }
miden-tx-batch = { workspace = true }
rand = { workspace = true }
semver = { workspace = true }
Expand All @@ -49,7 +50,6 @@ fs-err = { workspace = true }
miden-node-tracing = { features = ["tracing-forest"], workspace = true }
miden-node-utils = { features = ["testing-prover"], workspace = true }
miden-protocol = { default-features = true, features = ["testing"], workspace = true }
miden-standards = { workspace = true }
miden-testing = { workspace = true }
miden-tx = { features = ["concurrent", "testing"], workspace = true }
reqwest = { workspace = true }
Expand Down
9 changes: 8 additions & 1 deletion crates/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ mod server;
#[cfg(test)]
mod tests;

pub use server::{PreAuthSubmission, Rpc, RpcMode, SequencerInternal, ValidatorClients};
pub use server::{
AccountAdmission,
PreAuthSubmission,
Rpc,
RpcMode,
SequencerInternal,
ValidatorClients,
};

// CONSTANTS
// =================================================================================================
Expand Down
63 changes: 63 additions & 0 deletions crates/rpc/src/server/admission.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::sync::Arc;

use miden_node_store::allowlist::AccountAllowlist;
use miden_node_tracing::{error, miden_instrument};
use miden_protocol::account::{Account, AccountUpdateDetails};
use miden_protocol::transaction::TxAccountUpdate;
use miden_standards::account::auth::NetworkAccount;
use tonic::Status;

use crate::{COMPONENT, LOG_TARGET};

/// Account creation policy shared by the public and internal sequencer APIs.
#[derive(Clone)]
pub struct AccountAdmission {
pub(crate) allowlist: Arc<AccountAllowlist>,
disabled: bool,
}

impl AccountAdmission {
pub fn enabled(allowlist: Arc<AccountAllowlist>) -> Self {
Self { allowlist, disabled: false }
}

pub fn disabled(allowlist: Arc<AccountAllowlist>) -> Self {
Self { allowlist, disabled: true }
}

/// Rejects the submission if it creates an unregistered, non-network account.
#[miden_instrument(
target = COMPONENT,
name = "account_admission.check",
fields(account.id = update.account_id()),
err,
)]
pub(crate) async fn check(&self, update: &TxAccountUpdate) -> tonic::Result<()> {
if self.disabled || !update.initial_state_commitment().is_empty() {
return Ok(());
}

// New public accounts include their full state. Use the store's network-account
// classification rule before the account exists on chain.
if let AccountUpdateDetails::Public(patch) = update.details() {
let account = Account::try_from(patch)
.map_err(|error| Status::invalid_argument(error.to_string()))?;
if NetworkAccount::new(account).is_ok() {
return Ok(());
}
}

let account_id = update.account_id();
let registered = self.allowlist.contains_account(account_id).await.map_err(|err| {
error!(err, target: LOG_TARGET, "Account allowlist lookup failed");
Status::internal("account allowlist lookup failed")
})?;
if !registered {
return Err(Status::permission_denied(format!(
"account {account_id} is not registered"
)));
}

Ok(())
}
}
3 changes: 2 additions & 1 deletion crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use tonic::metadata::MetadataMap;
use tonic::{IntoRequest, Request, Status};

use crate::server::api::subscription::{IpBanList, MAX_REPLICA_SUBSCRIPTIONS};
use crate::server::{NetworkTxAuth, RpcBackend};
use crate::server::{AccountAdmission, NetworkTxAuth, RpcBackend};
use crate::{COMPONENT, LOG_TARGET};

// VALIDATOR FAN-OUT
Expand Down Expand Up @@ -275,6 +275,7 @@ impl RpcService {
pub(crate) struct SequencerInternalService {
pub(crate) state: Arc<State>,
pub(crate) block_producer: BlockProducerApi,
pub(crate) account_admission: AccountAdmission,
}

// HELPERS
Expand Down
27 changes: 14 additions & 13 deletions crates/rpc/src/server/api/register_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,20 @@ impl proto::server::rpc_api::RegisterAccount for RpcService {
.map_err(|error| Status::invalid_argument(error.to_string()))?;

match &self.backend {
RpcBackend::Sequencer { allowlist, .. } => {
allowlist.register_account(invitation, account_id).await.map(|_| ()).map_err(
|error| {
let code = match &error {
AllowlistError::InvitationNotFound => Code::NotFound,
AllowlistError::InvitationAlreadyUsed
| AllowlistError::AccountAlreadyRegistered(_) => Code::AlreadyExists,
AllowlistError::Database(_) => Code::Internal,
};
Status::new(code, error.as_report())
},
)
},
RpcBackend::Sequencer { account_admission, .. } => account_admission
.allowlist
.register_account(invitation, account_id)
.await
.map(|_| ())
.map_err(|error| {
let code = match &error {
AllowlistError::InvitationNotFound => Code::NotFound,
AllowlistError::InvitationAlreadyUsed
| AllowlistError::AccountAlreadyRegistered(_) => Code::AlreadyExists,
AllowlistError::Database(_) => Code::Internal,
};
Status::new(code, error.as_report())
}),
RpcBackend::FullNode { source_rpc, .. } => {
let mut request = Request::new(request);
if let Some(accept) = metadata.get(http::header::ACCEPT.as_str()) {
Expand Down
4 changes: 4 additions & 0 deletions crates/rpc/src/server/api/submit_auth_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ impl sequencer_api::SubmitAuthenticatedTx for SequencerInternalService {
_metadata: &tonic::metadata::MetadataMap,
_extensions: &tonic::codegen::http::Extensions,
) -> tonic::Result<Self::Output> {
self.account_admission
.check(tx.raw_proven_transaction().account_update())
.await?;

let (block_num, commitment) = tx.reference_block();
let reference_header = self
.state
Expand Down
4 changes: 4 additions & 0 deletions crates/rpc/src/server/api/submit_auth_tx_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ impl sequencer_api::SubmitAuthenticatedTxBatch for SequencerInternalService {
Status::internal(format!("authenticated batch decoding task failed: {err}"))
})??;

for tx in batch.transactions() {
self.account_admission.check(tx.account_update()).await?;
}

self.block_producer
.submit_authenticated_tx_batch(batch, inputs)
.await
Expand Down
4 changes: 4 additions & 0 deletions crates/rpc/src/server/api/submit_proven_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ impl proto::server::rpc_api::SubmitProvenTx for RpcService {

debug!(target: LOG_TARGET, "Submitting transaction");

if let RpcBackend::Sequencer { account_admission, .. } = &self.backend {
account_admission.check(tx.account_update()).await?;
}

// Verify the reference block is actually part of the chain.
let reference_header = self
.verify_reference_commitment(tx.ref_block_num(), tx.ref_block_commitment())
Expand Down
6 changes: 6 additions & 0 deletions crates/rpc/src/server/api/submit_proven_tx_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ impl proto::server::rpc_api::SubmitProvenTxBatch for RpcService {

debug!(target: LOG_TARGET, "Submitting transaction batch");

if let RpcBackend::Sequencer { account_admission, .. } = &self.backend {
for tx in proposed_batch.transactions() {
account_admission.check(tx.account_update()).await?;
}
}

// Verify the reference block is actually part of the chain.
self.verify_reference_commitment(
proven_batch.reference_block_num(),
Expand Down
27 changes: 18 additions & 9 deletions crates/rpc/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use miden_node_proto::clients::{
};
use miden_node_proto::server::{rpc_api, sequencer_api};
use miden_node_proto_build::rpc_api_descriptor;
use miden_node_store::allowlist::AccountAllowlist;
use miden_node_store::state::{BlockWriter, ProofWriter, State};
use miden_node_tracing::grpc::grpc_trace_fn;
use miden_node_tracing::info;
Expand All @@ -38,9 +37,12 @@ use crate::server::api::SequencerInternalService;
use crate::server::health::HealthCheckLayer;

mod accept;
mod admission;
pub(crate) mod api;
mod health;

pub use admission::AccountAdmission;

/// The RPC server component.
///
/// On startup, binds to the provided listener and starts serving the RPC API.
Expand Down Expand Up @@ -74,7 +76,7 @@ pub enum RpcMode {
Sequencer {
block_producer: Box<BlockProducerApi>,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
account_admission: AccountAdmission,
},
/// Full-node RPC.
///
Expand Down Expand Up @@ -106,7 +108,7 @@ pub(crate) enum RpcBackend {
Sequencer {
block_producer: Box<BlockProducerApi>,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
account_admission: AccountAdmission,
},
FullNode {
source_rpc: Box<SourceRpcClient>,
Expand All @@ -122,12 +124,12 @@ impl RpcBackend {
pub(crate) fn sequencer(
block_producer: BlockProducerApi,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
account_admission: AccountAdmission,
) -> Self {
Self::Sequencer {
block_producer: Box::new(block_producer),
validators,
allowlist,
account_admission,
}
}

Expand Down Expand Up @@ -211,12 +213,12 @@ impl RpcMode {
pub fn sequencer(
block_producer: BlockProducerApi,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
account_admission: AccountAdmission,
) -> Self {
Self::Sequencer {
block_producer: Box::new(block_producer),
validators,
allowlist,
account_admission,
}
}

Expand Down Expand Up @@ -247,10 +249,14 @@ impl RpcMode {
/// [`RpcService`](api::RpcService).
fn backend(&self) -> RpcBackend {
match self {
Self::Sequencer { block_producer, validators, allowlist } => RpcBackend::Sequencer {
Self::Sequencer {
block_producer,
validators,
account_admission,
} => RpcBackend::Sequencer {
block_producer: block_producer.clone(),
validators: validators.clone(),
allowlist: Arc::clone(allowlist),
account_admission: account_admission.clone(),
},
Self::FullNode { source_rpc, pre_auth, .. } => RpcBackend::FullNode {
source_rpc: source_rpc.clone(),
Expand Down Expand Up @@ -459,6 +465,8 @@ pub struct SequencerInternal {
pub state: Arc<State>,
/// The in-process block producer API submissions are forwarded to.
pub block_producer: BlockProducerApi,
/// Account creation policy shared with the public RPC API.
pub account_admission: AccountAdmission,
/// gRPC server options for internal services (timeouts).
pub grpc_options: GrpcOptions,
}
Expand All @@ -482,6 +490,7 @@ impl SequencerInternal {
let service = SequencerInternalService {
state: self.state,
block_producer: self.block_producer,
account_admission: self.account_admission,
};

// Note: deliberately no accept-header / auth layers; this is a private, trusted interface
Expand Down
Loading
Loading