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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bin/node/src/admin/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async fn admin_registration_workflow() {
assert_eq!(registered["allowlisted_at"], unused["allowlisted_at"]);
assert!(registered.get("invitation_code").is_none());
assert_eq!(
allowlist.invitation_status(InvitationCode::new(b"abc").unwrap()).await.unwrap(),
allowlist.invitation_status(InvitationCode::new("abc").unwrap()).await.unwrap(),
miden_node_store::allowlist::InvitationStatus::Registered(account(0))
);

Expand Down
4 changes: 2 additions & 2 deletions bin/node/src/commands/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ impl MigrateCommand {
Db::migrate(data_directory.database_path())
.context("failed to apply store database migrations")?;

// Only sequencer admin startup creates this optional database. Migration must also work for
// full nodes that do not have it.
// Only sequencer startup creates this optional database. Migration must also work for full
// nodes that do not have it.
let allowlist_path = data_directory.allowlist_database_path();
if fs_err::exists(&allowlist_path).context("failed to check account allowlist database")? {
AccountAllowlist::migrate(allowlist_path)
Expand Down
35 changes: 18 additions & 17 deletions bin/node/src/commands/modes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl SequencerCommand {
remote_prover_monitor(self.block_producer.batch.prover_url.as_ref())?;
let block_prover_monitor =
remote_prover_monitor(self.block_producer.block_prover.url.as_ref())?;
let allowlist = Arc::new(self.load_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 @@ -102,7 +103,11 @@ impl SequencerCommand {
let rpc = Rpc {
listener: bind_rpc(runtime.rpc_listen).await?,
state: Arc::clone(&state),
mode: RpcMode::sequencer(block_producer.clone(), validator_clients),
mode: RpcMode::sequencer(
block_producer.clone(),
validator_clients,
Arc::clone(&allowlist),
),
ntx_builder: Some(ntx_builder_client),
grpc_options: runtime.grpc_options,
network_tx_auth,
Expand All @@ -114,22 +119,6 @@ impl SequencerCommand {
if let Some(address) = self.admin_listen {
let shutdown = shutdown.clone();
tasks.spawn("sequencer admin API", async move {
let data_directory = DataDirectory::load(runtime.data_directory)?;
// Chain bootstrap does not create the optional allowlist database. A promoted full
// node can reach startup without it.
//
// This is okay because this is a temporary database.
let allowlist_path = data_directory.allowlist_database_path();
if !fs_err::exists(&allowlist_path)
.context("failed to check account allowlist database")?
{
AccountAllowlist::bootstrap(&allowlist_path)
.context("failed to bootstrap account allowlist database")?;
}
let allowlist = Arc::new(
AccountAllowlist::load(allowlist_path)
.context("failed to load account allowlist database")?,
);
AdminServer::bind(address, allowlist).await?.serve(shutdown).await
});
}
Expand Down Expand Up @@ -170,6 +159,18 @@ impl SequencerCommand {
tasks.join_next_or_cancelled(shutdown).await
}

fn load_allowlist(&self) -> anyhow::Result<AccountAllowlist> {
let data_directory = DataDirectory::load(self.runtime.data_directory.clone())?;
// Chain bootstrap does not create the allowlist database. A promoted full node can reach
// sequencer startup without it.
let allowlist_path = data_directory.allowlist_database_path();
if !fs_err::exists(&allowlist_path).context("failed to check account allowlist database")? {
AccountAllowlist::bootstrap(&allowlist_path)
.context("failed to bootstrap account allowlist database")?;
}
AccountAllowlist::load(allowlist_path).context("failed to load account allowlist database")
}

fn log_starting(&self) {
info!(
target: crate::LOG_TARGET,
Expand Down
1 change: 1 addition & 0 deletions crates/proto/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn generate_bindings(file_descriptors: &FileDescriptorSet, dst_dir: &Path) -> mi
for &(proto_path, rust_path) in miden_objects::EXTERN_PATHS {
prost_config.extern_path(proto_path, rust_path);
}
prost_config.skip_debug(["RegisterAccountRequest"]);

// Generate the stub of the user facing server from its proto file
tonic_prost_build::configure()
Expand Down
13 changes: 13 additions & 0 deletions crates/proto/src/domain/account.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::{Debug, Formatter};

use miden_node_utils::limiter::{QueryParamLimiter, QueryParamStorageMapKeyTotalLimit};
use miden_protocol::Word;
#[cfg(test)]
Expand Down Expand Up @@ -44,6 +46,17 @@ pub struct AccountInfo {
pub details: Option<Account>,
}

// REGISTER ACCOUNT REQUEST
// ================================================================================================

impl Debug for proto::rpc::RegisterAccountRequest {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RegisterAccountRequest")
.field("account_id", &self.account_id)
.finish_non_exhaustive()
}
}

// ACCOUNT REQUEST
// ================================================================================================

Expand Down
11 changes: 11 additions & 0 deletions crates/proto/src/domain/account/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ use miden_protocol::account::StorageMapKey;

use super::*;

#[test]
fn registration_request_debug_hides_invitation_code() {
let code = "private invitation code";
let request = proto::rpc::RegisterAccountRequest {
invitation_code: code.to_owned(),
account_id: None,
};
let debug = format!("{request:?}");
assert!(!debug.contains(code));
}

fn word_from_u32(arr: [u32; 4]) -> Word {
Word::from(arr)
}
Expand Down
1 change: 1 addition & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ tower-http = { features = ["trace"], workspace = true }
url = { workspace = true }

[dev-dependencies]
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 }
Expand Down
1 change: 1 addition & 0 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ mod get_network_note_status;
mod get_note_script_by_root;
mod get_notes_by_id;
mod get_transaction_encryption_key;
mod register_account;
mod status;
mod submit_auth_tx;
mod submit_auth_tx_batch;
Expand Down
64 changes: 64 additions & 0 deletions crates/rpc/src/server/api/register_account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use miden_node_proto::generated as proto;
use miden_node_store::allowlist::{AllowlistError, InvitationCode};
use miden_node_tracing::{ErrorReport, miden_instrument, miden_span_record};
use miden_protocol::account::AccountId;
use tonic::{Code, Request, Status};

use super::{RpcBackend, RpcService};
use crate::COMPONENT;

#[tonic::async_trait]
impl proto::server::rpc_api::RegisterAccount for RpcService {
type Input = proto::rpc::RegisterAccountRequest;
type Output = ();

fn decode(request: proto::rpc::RegisterAccountRequest) -> tonic::Result<Self::Input> {
Ok(request)
}

fn encode((): Self::Output) -> tonic::Result<()> {
Ok(())
}

#[miden_instrument(target = COMPONENT, name = "register_account", err)]
async fn handle(
&self,
request: Self::Input,
metadata: &tonic::metadata::MetadataMap,
_extensions: &tonic::codegen::http::Extensions,
) -> tonic::Result<Self::Output> {
let account_id: AccountId = request
.account_id
.clone()
.ok_or_else(|| Status::invalid_argument("missing account_id"))?
.try_into()
.map_err(|_| Status::invalid_argument("invalid account_id"))?;
miden_span_record!(account.id = account_id);

let invitation = InvitationCode::new(&request.invitation_code)
.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::FullNode { source_rpc, .. } => {
let mut request = Request::new(request);
if let Some(accept) = metadata.get(http::header::ACCEPT.as_str()) {
request.metadata_mut().insert(http::header::ACCEPT.as_str(), accept.clone());
}
source_rpc.as_ref().clone().register_account(request).await.map(|_| ())
},
}
}
}
2 changes: 1 addition & 1 deletion crates/rpc/src/server/api/submit_proven_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl proto::server::rpc_api::SubmitProvenTx for RpcService {
})??;

match &self.backend {
RpcBackend::Sequencer { block_producer, validators } => {
RpcBackend::Sequencer { block_producer, validators, .. } => {
submit_tx_to_validators(validators.as_slice(), &request).await?;
block_producer
.submit_proven_tx(rebuilt_tx)
Expand Down
2 changes: 1 addition & 1 deletion crates/rpc/src/server/api/submit_proven_tx_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl proto::server::rpc_api::SubmitProvenTxBatch for RpcService {
verify_batch_proof(proven_batch, &proposed_batch).await?;

match &self.backend {
RpcBackend::Sequencer { block_producer, validators } => {
RpcBackend::Sequencer { block_producer, validators, .. } => {
submit_batch_to_validators(
validators.as_slice(),
&proposed_batch,
Expand Down
18 changes: 15 additions & 3 deletions crates/rpc/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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 Down Expand Up @@ -73,6 +74,7 @@ pub enum RpcMode {
Sequencer {
block_producer: Box<BlockProducerApi>,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
},
/// Full-node RPC.
///
Expand All @@ -99,11 +101,12 @@ pub enum RpcMode {
/// `Clone` because it is cloned once into `RpcService` and then read on every request; it never
/// carries the full-node's store write capabilities ([`RpcMode`] does), since no handler needs
/// them — those are consumed once by the sync loop at startup.
#[derive(Clone, Debug)]
#[derive(Clone)]
pub(crate) enum RpcBackend {
Sequencer {
block_producer: Box<BlockProducerApi>,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
},
FullNode {
source_rpc: Box<SourceRpcClient>,
Expand All @@ -119,10 +122,12 @@ impl RpcBackend {
pub(crate) fn sequencer(
block_producer: BlockProducerApi,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
) -> Self {
Self::Sequencer {
block_producer: Box::new(block_producer),
validators,
allowlist,
}
}

Expand Down Expand Up @@ -203,10 +208,15 @@ impl PreAuthSubmission {
}

impl RpcMode {
pub fn sequencer(block_producer: BlockProducerApi, validators: ValidatorClients) -> Self {
pub fn sequencer(
block_producer: BlockProducerApi,
validators: ValidatorClients,
allowlist: Arc<AccountAllowlist>,
) -> Self {
Self::Sequencer {
block_producer: Box::new(block_producer),
validators,
allowlist,
}
}

Expand Down Expand Up @@ -237,9 +247,10 @@ impl RpcMode {
/// [`RpcService`](api::RpcService).
fn backend(&self) -> RpcBackend {
match self {
Self::Sequencer { block_producer, validators } => RpcBackend::Sequencer {
Self::Sequencer { block_producer, validators, allowlist } => RpcBackend::Sequencer {
block_producer: block_producer.clone(),
validators: validators.clone(),
allowlist: Arc::clone(allowlist),
},
Self::FullNode { source_rpc, pre_auth, .. } => RpcBackend::FullNode {
source_rpc: source_rpc.clone(),
Expand Down Expand Up @@ -353,6 +364,7 @@ impl Rpc {
// CORS rejection).
.layer(
AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
.with_genesis_enforced_method("RegisterAccount")
.with_genesis_enforced_method("SubmitProvenTx")
.with_genesis_enforced_method("SubmitProvenTxBatch"),
)
Expand Down
Loading
Loading