Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement get account proof endpoint #556

Open
wants to merge 17 commits into
base: next
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.6.0 (TBD)

* Allow to set expiration delta for `TransactionRequest` (#553).
* Implemented `GetAccountProof` endpoint (#556)
* Added WASM consumable notes API + improved note models (#561).
* [BREAKING] Refactored `OutputNoteRecord` to use states and transitions for updates (#551).
* Added better error handling for WASM sync state (#558).
Expand Down
14 changes: 12 additions & 2 deletions crates/rust-client/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use miden_objects::{
notes::{Note, NoteId, NoteTag},
testing::notes::NoteBuilder,
transaction::{InputNote, ProvenTransaction},
BlockHeader, Felt, Word,
BlockHeader, Digest, Felt, Word,
};
use miden_tx::{testing::mock_chain::MockChain, LocalTransactionProver};
use rand::Rng;
Expand All @@ -32,7 +32,8 @@ use crate::{
note::NoteSyncRecord,
responses::{NullifierUpdate, SyncNoteResponse, SyncStateResponse},
},
AccountDetails, NodeRpcClient, NoteDetails, NoteInclusionDetails, RpcError, StateSyncInfo,
AccountDetails, AccountProofs, NodeRpcClient, NoteDetails, NoteInclusionDetails, RpcError,
StateSyncInfo,
},
store::{
sqlite_store::{config::SqliteStoreConfig, SqliteStore},
Expand Down Expand Up @@ -293,6 +294,15 @@ impl NodeRpcClient for MockRpcApi {
panic!("shouldn't be used for now")
}

async fn get_account_proofs(
&mut self,
_account_ids: &[AccountId],
_code_commitments: &[Digest],
_include_headers: bool,
) -> Result<AccountProofs, RpcError> {
todo!();
}

async fn check_nullifiers_by_prefix(
&mut self,
_prefix: &[u16],
Expand Down
75 changes: 71 additions & 4 deletions crates/rust-client/src/rpc/domain/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
use alloc::string::{String, ToString};
use core::fmt::{self, Debug, Display, Formatter};

use miden_objects::accounts::AccountId;
use miden_objects::{
accounts::{AccountCode, AccountHeader, AccountId, AccountStorageHeader},
Felt,
};
use miden_tx::utils::Deserializable;

#[cfg(feature = "tonic")]
use crate::rpc::tonic_client::generated::account::AccountId as ProtoAccountId;
use crate::rpc::{
tonic_client::generated::account::AccountHeader as ProtoAccountHeader,
tonic_client::generated::account::AccountId as ProtoAccountId,
tonic_client::generated::responses::AccountStateHeader as ProtoAccountStateHeader,
RpcConversionError,
};
#[cfg(feature = "web-tonic")]
use crate::rpc::web_tonic_client::generated::account::AccountId as ProtoAccountId;
use crate::rpc::RpcConversionError;
use crate::rpc::{
web_tonic_client::generated::account::AccountHeader as ProtoAccountHeader,
web_tonic_client::generated::account::AccountId as ProtoAccountId,
web_tonic_client::generated::responses::AccountStateHeader as ProtoAccountStateHeader,
RpcConversionError,
};
use crate::rpc::{RpcError, StateHeaders};

// ACCOUNT ID
// ================================================================================================
Expand Down Expand Up @@ -42,3 +57,55 @@ impl TryFrom<ProtoAccountId> for AccountId {
account_id.id.try_into().map_err(|_| RpcConversionError::NotAValidFelt)
}
}

// ACCOUNT HEADER
// ================================================================================================

impl ProtoAccountHeader {
pub fn into_domain(self, account_id: AccountId) -> Result<AccountHeader, RpcError> {
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
let ProtoAccountHeader {
nonce,
vault_root,
storage_commitment,
code_commitment,
} = self;
let vault_root = vault_root
.ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.VaultRoot")))?
.try_into()?;
let storage_commitment = storage_commitment
.ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.StorageCommitment")))?
.try_into()?;
let code_commitment = code_commitment
.ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.CodeCommitment")))?
.try_into()?;

Ok(AccountHeader::new(
account_id,
Felt::new(nonce),
vault_root,
storage_commitment,
code_commitment,
))
}
}

// FROM PROTO ACCOUNT HEADERS
// ------------------------------------------------------------------------------------------------

impl ProtoAccountStateHeader {
pub fn into_domain(self, account_id: AccountId) -> Result<StateHeaders, RpcError> {
let ProtoAccountStateHeader { header, storage_header, account_code } = self;
let account_header =
header.ok_or(RpcError::ExpectedDataMissing("Account.StateHeader".to_string()))?;

let storage_header = AccountStorageHeader::read_from_bytes(&storage_header)?;

let code = account_code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;

Ok(StateHeaders {
account_header: account_header.into_domain(account_id)?,
storage_header,
code,
})
}
}
26 changes: 15 additions & 11 deletions crates/rust-client/src/rpc/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,38 @@ use miden_objects::{accounts::AccountId, utils::DeserializationError, NoteError}

#[derive(Debug)]
pub enum RpcError {
AccountUpdateForPrivateAccountReceived(AccountId),
ConnectionError(String),
DeserializationError(String),
ExpectedFieldMissing(String),
AccountUpdateForPrivateAccountReceived(AccountId),
ExpectedDataMissing(String),
InvalidResponse(String),
RequestError(String, String),
}

impl fmt::Display for RpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RpcError::AccountUpdateForPrivateAccountReceived(account_id) => {
write!(
f,
"RPC API response contained an update for a private account: {}",
account_id.to_hex()
)
},
RpcError::ConnectionError(err) => {
write!(f, "failed to connect to the API server: {err}")
},
RpcError::DeserializationError(err) => {
write!(f, "failed to deserialize RPC data: {err}")
},
RpcError::ExpectedFieldMissing(err) => {
write!(f, "rpc API response missing an expected field: {err}")
RpcError::ExpectedDataMissing(err) => {
write!(f, "RPC API response missing an expected field: {err}")
},
RpcError::AccountUpdateForPrivateAccountReceived(account_id) => {
write!(
f,
"rpc API response contained an update for a private account: {}",
account_id.to_hex()
)
RpcError::InvalidResponse(err) => {
write!(f, "RPC API response is invalidw: {err}")
},
RpcError::RequestError(endpoint, err) => {
write!(f, "rpc request failed for {endpoint}: {err}")
write!(f, "RPC request failed for {endpoint}: {err}")
},
}
}
Expand Down
111 changes: 110 additions & 1 deletion crates/rust-client/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod errors;
pub(crate) use errors::RpcConversionError;
pub use errors::RpcError;
use miden_objects::{
accounts::{Account, AccountId},
accounts::{Account, AccountCode, AccountHeader, AccountId, AccountStorageHeader},
crypto::merkle::{MerklePath, MmrDelta, MmrProof},
notes::{Note, NoteId, NoteMetadata, NoteTag, Nullifier},
transaction::{ProvenTransaction, TransactionId},
Expand Down Expand Up @@ -130,6 +130,105 @@ impl NoteInclusionDetails {
}
}

// ACCOUNT PROOF
// ================================================================================================

/// List of account proofs related to a specific block number.
pub type AccountProofs = (u32, Vec<AccountProof>);
igamigo marked this conversation as resolved.
Show resolved Hide resolved

/// Account state headers.
pub struct StateHeaders {
pub account_header: AccountHeader,
pub storage_header: AccountStorageHeader,
pub code: Option<AccountCode>,
}

/// Represents a proof of existence of an account's state at a specific block number.
pub struct AccountProof {
/// Account ID.
account_id: AccountId,
/// Authentication path from the `account_root` of the block header to the account.
merkle_proof: MerklePath,
/// Account hash for the current state.
account_hash: Digest,
/// State headers of public accounts.
state_headers: Option<StateHeaders>,
}

impl AccountProof {
pub fn new(
account_id: AccountId,
merkle_proof: MerklePath,
account_hash: Digest,
state_headers: Option<StateHeaders>,
) -> Result<Self, AccountProofError> {
if let Some(state_headers) = &state_headers {
if state_headers.account_header.hash() != account_hash {
return Err(AccountProofError::InconsistentAccountHash);
}
if account_id != state_headers.account_header.id() {
return Err(AccountProofError::InconsistentAccountId);
}
}

Ok(Self {
account_id,
merkle_proof,
account_hash,
state_headers,
})
}

pub fn account_id(&self) -> AccountId {
self.account_id
}

pub fn account_header(&self) -> Option<&AccountHeader> {
self.state_headers.as_ref().map(|headers| &headers.account_header)
}

pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
self.state_headers.as_ref().map(|headers| &headers.storage_header)
}

pub fn account_code(&self) -> Option<&AccountCode> {
if let Some(StateHeaders { code, .. }) = &self.state_headers {
code.as_ref()
} else {
None
}
}

pub fn code_commitment(&self) -> Option<Digest> {
match &self.state_headers {
Some(StateHeaders { code: Some(code), .. }) => Some(code.commitment()),
_ => None,
}
}

pub fn account_hash(&self) -> Digest {
self.account_hash
}

pub fn merkle_proof(&self) -> &MerklePath {
&self.merkle_proof
}
}

pub enum AccountProofError {
InconsistentAccountHash,
InconsistentAccountId,
}

impl fmt::Display for AccountProofError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AccountProofError::InconsistentAccountHash => write!(f,"The received account hash does not match the received account header's account hash"),
AccountProofError::InconsistentAccountId => write!(f,"The received account ID does not match the received account header's ID"),
}
}
}

// NODE RPC CLIENT TRAIT
// ================================================================================================

Expand Down Expand Up @@ -207,6 +306,14 @@ pub trait NodeRpcClient {
prefix: &[u16],
) -> Result<Vec<(Nullifier, u32)>, RpcError>;

/// Fetches the current account state, using th `/GetAccountProofs` RPC endpoint.
async fn get_account_proofs(
&mut self,
account_ids: &[AccountId],
code_commitments: &[Digest],
include_headers: bool,
) -> Result<AccountProofs, RpcError>;

/// Fetches the commit height where the nullifier was consumed. If the nullifier is not found,
/// then `None` is returned.
///
Expand Down Expand Up @@ -341,6 +448,7 @@ impl CommittedNote {
pub enum NodeRpcClientEndpoint {
CheckNullifiersByPrefix,
GetAccountDetails,
GetAccountProofs,
GetBlockHeaderByNumber,
SyncState,
SubmitProvenTx,
Expand All @@ -354,6 +462,7 @@ impl fmt::Display for NodeRpcClientEndpoint {
write!(f, "check_nullifiers_by_prefix")
},
NodeRpcClientEndpoint::GetAccountDetails => write!(f, "get_account_details"),
NodeRpcClientEndpoint::GetAccountProofs => write!(f, "get_account_proofs"),
NodeRpcClientEndpoint::GetBlockHeaderByNumber => {
write!(f, "get_block_header_by_number")
},
Expand Down
Loading