use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use domain::account::{
AccountDetails,
AccountProof,
GetAccountRequest,
StorageMapEntries,
StorageMapEntry,
StorageMapFetch,
VaultFetch,
};
use domain::note::{FetchedNote, NoteSyncBlock, SyncedNoteDetails};
use domain::nullifier::NullifierUpdate;
use domain::sync::{ChainMmrInfo, SyncTarget};
use miden_protocol::Word;
use miden_protocol::account::{Account, AccountId};
use miden_protocol::address::NetworkId;
use miden_protocol::batch::{ProposedBatch, ProvenBatch};
use miden_protocol::block::{BlockHeader, BlockNumber, ProvenBlock};
use miden_protocol::crypto::merkle::mmr::MmrProof;
use miden_protocol::note::{NoteId, NoteMetadata, NoteScript, NoteTag, NoteType, Nullifier};
use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
use crate::rpc::domain::storage_map::StorageMapInfo;
pub mod domain;
mod errors;
pub use errors::*;
mod endpoint;
pub(crate) use domain::limits::RPC_LIMITS_STORE_SETTING;
pub use domain::limits::RpcLimits;
pub use domain::status::{NetworkNoteStatus, NetworkNoteStatusInfo, RpcStatusInfo};
pub use endpoint::Endpoint;
#[cfg(not(feature = "testing"))]
mod generated;
#[cfg(feature = "testing")]
pub mod generated;
#[cfg(feature = "tonic")]
mod tonic_client;
#[cfg(feature = "tonic")]
pub use tonic_client::GrpcClient;
use crate::rpc::domain::account_vault::AccountVaultInfo;
use crate::rpc::domain::transaction::TransactionRecord;
use crate::store::InputNoteRecord;
use crate::store::input_note_states::UnverifiedNoteState;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum AccountStateAt {
#[default]
ChainTip,
Block(BlockNumber),
}
fn metadata_has_attachments(metadata: &NoteMetadata) -> bool {
metadata.attachment_headers().iter().any(|header| header.scheme().is_some())
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
pub trait NodeRpcClient: Send + Sync {
async fn set_genesis_commitment(&self, commitment: Word) -> Result<(), RpcError>;
fn has_genesis_commitment(&self) -> Option<Word>;
async fn submit_proven_transaction(
&self,
proven_transaction: ProvenTransaction,
transaction_inputs: TransactionInputs,
) -> Result<BlockNumber, RpcError>;
async fn submit_proven_batch(
&self,
proven_batch: ProvenBatch,
proposed_batch: ProposedBatch,
transaction_inputs: Vec<TransactionInputs>,
) -> Result<BlockNumber, RpcError>;
async fn get_block_header_by_number(
&self,
block_num: Option<BlockNumber>,
include_mmr_proof: bool,
) -> Result<(BlockHeader, Option<MmrProof>), RpcError>;
async fn get_block_by_number(
&self,
block_num: BlockNumber,
include_proof: bool,
) -> Result<ProvenBlock, RpcError>;
async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError>;
async fn sync_chain_mmr(
&self,
current_block_height: BlockNumber,
upper_bound: SyncTarget,
) -> Result<ChainMmrInfo, RpcError>;
async fn get_account_details(
&self,
account_id: AccountId,
) -> Result<Option<Account>, RpcError> {
if !account_id.is_public() {
return Ok(None);
}
let (block_number, mut proof) = self
.get_account(
account_id,
GetAccountRequest::new()
.with_storage(StorageMapFetch::All)
.with_vault(VaultFetch::Always),
)
.await?;
if let Some(details) = proof.details_mut() {
self.resolve_oversize_vault(account_id, block_number, details).await?;
self.resolve_oversize_storage_maps(account_id, block_number, details).await?;
}
let details = proof.into_details().ok_or(RpcError::ExpectedDataMissing(
"public account returned without details".into(),
))?;
Ok(Some(Account::try_from(&details)?))
}
async fn sync_notes(
&self,
block_from: BlockNumber,
block_to: BlockNumber,
note_tags: &BTreeSet<NoteTag>,
) -> Result<Vec<NoteSyncBlock>, RpcError>;
async fn sync_notes_with_details(
&self,
block_from: BlockNumber,
block_to: BlockNumber,
note_tags: &BTreeSet<NoteTag>,
) -> Result<(Vec<NoteSyncBlock>, BTreeMap<NoteId, SyncedNoteDetails>), RpcError> {
let blocks = self.sync_notes(block_from, block_to, note_tags).await?;
let note_ids: Vec<NoteId> = blocks
.iter()
.flat_map(|b| b.notes.values())
.filter(|n| n.note_type() == NoteType::Public || metadata_has_attachments(n.metadata()))
.map(|n| *n.note_id())
.collect();
let mut synced_notes: BTreeMap<NoteId, SyncedNoteDetails> = BTreeMap::new();
if !note_ids.is_empty() {
let fetched = self.get_notes_by_id(¬e_ids).await?;
for fetched_note in fetched {
match fetched_note {
FetchedNote::Public(note, _) => {
synced_notes.insert(note.id(), SyncedNoteDetails::Public(note));
},
FetchedNote::Private(note_id, _, attachments, _) => {
let attachments = (!attachments.is_empty()).then_some(attachments);
synced_notes.insert(note_id, SyncedNoteDetails::Private(attachments));
},
}
}
}
Ok((blocks, synced_notes))
}
async fn sync_nullifiers(
&self,
prefix: &[u16],
block_from: BlockNumber,
block_to: BlockNumber,
) -> Result<Vec<NullifierUpdate>, RpcError>;
async fn get_account(
&self,
account_id: AccountId,
request: GetAccountRequest,
) -> Result<(BlockNumber, AccountProof), RpcError>;
async fn resolve_oversize_vault(
&self,
account_id: AccountId,
block_to: BlockNumber,
details: &mut AccountDetails,
) -> Result<(), RpcError> {
if !details.vault_details.too_many_assets {
return Ok(());
}
let vault_info =
self.sync_account_vault(BlockNumber::GENESIS, block_to, account_id).await?;
let mut updates = vault_info.updates;
updates.sort_by_key(|u| u.block_num);
details.vault_details.assets = updates
.into_iter()
.map(|u| (u.vault_key, u.asset))
.collect::<BTreeMap<_, _>>()
.into_values()
.flatten()
.collect();
details.vault_details.too_many_assets = false;
Ok(())
}
async fn resolve_oversize_storage_maps(
&self,
account_id: AccountId,
block_to: BlockNumber,
details: &mut AccountDetails,
) -> Result<(), RpcError> {
if !details.storage_details.map_details.iter().any(|m| m.too_many_entries) {
return Ok(());
}
let info = self.sync_storage_maps(BlockNumber::GENESIS, block_to, account_id).await?;
for map_details in &mut details.storage_details.map_details {
if !map_details.too_many_entries {
continue;
}
let mut sorted: Vec<_> =
info.updates.iter().filter(|u| u.slot_name == map_details.slot_name).collect();
sorted.sort_by_key(|u| u.block_num);
let entries: Vec<StorageMapEntry> = sorted
.into_iter()
.map(|u| (u.key, u.value))
.collect::<BTreeMap<_, _>>()
.into_iter()
.map(|(key, value)| StorageMapEntry { key, value })
.collect();
map_details.too_many_entries = false;
map_details.entries = StorageMapEntries::AllEntries(entries);
}
Ok(())
}
async fn get_nullifier_commit_heights(
&self,
requested_nullifiers: BTreeSet<Nullifier>,
block_from: BlockNumber,
) -> Result<BTreeMap<Nullifier, Option<BlockNumber>>, RpcError> {
let prefixes: Vec<u16> =
requested_nullifiers.iter().map(crate::note::Nullifier::prefix).collect();
let (chain_tip, _) = self.get_block_header_by_number(None, false).await?;
let retrieved_nullifiers =
self.sync_nullifiers(&prefixes, block_from, chain_tip.block_num()).await?;
let mut nullifiers_height = BTreeMap::new();
for nullifier in requested_nullifiers {
if let Some(update) =
retrieved_nullifiers.iter().find(|update| update.nullifier == nullifier)
{
nullifiers_height.insert(nullifier, Some(update.block_num));
} else {
nullifiers_height.insert(nullifier, None);
}
}
Ok(nullifiers_height)
}
async fn get_public_note_records(
&self,
note_ids: &[NoteId],
current_timestamp: Option<u64>,
) -> Result<Vec<InputNoteRecord>, RpcError> {
if note_ids.is_empty() {
return Ok(vec![]);
}
let mut public_notes = Vec::with_capacity(note_ids.len());
let note_details = self.get_notes_by_id(note_ids).await?;
for detail in note_details {
if let FetchedNote::Public(note, inclusion_proof) = detail {
let state = UnverifiedNoteState {
metadata: *note.metadata(),
inclusion_proof,
}
.into();
let attachments = note.attachments().clone();
let note = InputNoteRecord::new(note.into(), attachments, current_timestamp, state);
public_notes.push(note);
}
}
Ok(public_notes)
}
async fn get_block_header_with_proof(
&self,
block_num: BlockNumber,
) -> Result<(BlockHeader, MmrProof), RpcError> {
let (header, proof) = self.get_block_header_by_number(Some(block_num), true).await?;
Ok((header, proof.ok_or(RpcError::ExpectedDataMissing(String::from("MmrProof")))?))
}
async fn get_note_by_id(&self, note_id: NoteId) -> Result<FetchedNote, RpcError> {
let notes = self.get_notes_by_id(&[note_id]).await?;
notes.into_iter().next().ok_or(RpcError::NoteNotFound(note_id))
}
async fn get_note_script_by_root(&self, root: Word) -> Result<Option<NoteScript>, RpcError>;
async fn sync_storage_maps(
&self,
block_from: BlockNumber,
block_to: BlockNumber,
account_id: AccountId,
) -> Result<StorageMapInfo, RpcError>;
async fn sync_account_vault(
&self,
block_from: BlockNumber,
block_to: BlockNumber,
account_id: AccountId,
) -> Result<AccountVaultInfo, RpcError>;
async fn sync_transactions(
&self,
block_from: BlockNumber,
block_to: BlockNumber,
account_ids: Vec<AccountId>,
) -> Result<Vec<TransactionRecord>, RpcError>;
async fn get_network_id(&self) -> Result<NetworkId, RpcError>;
async fn get_rpc_limits(&self) -> Result<RpcLimits, RpcError>;
fn has_rpc_limits(&self) -> Option<RpcLimits>;
async fn set_rpc_limits(&self, limits: RpcLimits);
async fn get_status_unversioned(&self) -> Result<RpcStatusInfo, RpcError>;
async fn get_network_note_status(
&self,
note_id: NoteId,
) -> Result<NetworkNoteStatusInfo, RpcError>;
}
#[derive(Debug, Clone, Copy)]
pub enum RpcEndpoint {
Status,
SyncNullifiers,
GetAccount,
GetBlockByNumber,
GetBlockHeaderByNumber,
GetNotesById,
SyncChainMmr,
SubmitProvenTx,
SubmitProvenBatch,
SyncNotes,
GetNoteScriptByRoot,
SyncStorageMaps,
SyncAccountVault,
SyncTransactions,
GetLimits,
GetNetworkNoteStatus,
}
impl RpcEndpoint {
pub fn proto_name(&self) -> &'static str {
match self {
RpcEndpoint::Status => "Status",
RpcEndpoint::SyncNullifiers => "SyncNullifiers",
RpcEndpoint::GetAccount => "GetAccount",
RpcEndpoint::GetBlockByNumber => "GetBlockByNumber",
RpcEndpoint::GetBlockHeaderByNumber => "GetBlockHeaderByNumber",
RpcEndpoint::GetNotesById => "GetNotesById",
RpcEndpoint::SyncChainMmr => "SyncChainMmr",
RpcEndpoint::SubmitProvenTx => "SubmitProvenTransaction",
RpcEndpoint::SubmitProvenBatch => "SubmitProvenBatch",
RpcEndpoint::SyncNotes => "SyncNotes",
RpcEndpoint::GetNoteScriptByRoot => "GetNoteScriptByRoot",
RpcEndpoint::SyncStorageMaps => "SyncStorageMaps",
RpcEndpoint::SyncAccountVault => "SyncAccountVault",
RpcEndpoint::SyncTransactions => "SyncTransactions",
RpcEndpoint::GetLimits => "GetLimits",
RpcEndpoint::GetNetworkNoteStatus => "GetNetworkNoteStatus",
}
}
}
impl fmt::Display for RpcEndpoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RpcEndpoint::Status => write!(f, "status"),
RpcEndpoint::SyncNullifiers => {
write!(f, "sync_nullifiers")
},
RpcEndpoint::GetAccount => write!(f, "get_account"),
RpcEndpoint::GetBlockByNumber => write!(f, "get_block_by_number"),
RpcEndpoint::GetBlockHeaderByNumber => {
write!(f, "get_block_header_by_number")
},
RpcEndpoint::GetNotesById => write!(f, "get_notes_by_id"),
RpcEndpoint::SyncChainMmr => write!(f, "sync_chain_mmr"),
RpcEndpoint::SubmitProvenTx => write!(f, "submit_proven_transaction"),
RpcEndpoint::SubmitProvenBatch => write!(f, "submit_proven_batch"),
RpcEndpoint::SyncNotes => write!(f, "sync_notes"),
RpcEndpoint::GetNoteScriptByRoot => write!(f, "get_note_script_by_root"),
RpcEndpoint::SyncStorageMaps => write!(f, "sync_storage_maps"),
RpcEndpoint::SyncAccountVault => write!(f, "sync_account_vault"),
RpcEndpoint::SyncTransactions => write!(f, "sync_transactions"),
RpcEndpoint::GetLimits => write!(f, "get_limits"),
RpcEndpoint::GetNetworkNoteStatus => write!(f, "get_network_note_status"),
}
}
}