Skip to content

Commit d8f56fe

Browse files
authored
feat: implement missing RPC endpoints (0xMiden#1362)
1 parent 4f0b9f1 commit d8f56fe

File tree

12 files changed

+819
-34
lines changed

12 files changed

+819
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* Refactored code into their own files and added `ProvenTransaction` and `TransactionStoreUpdate` bindings for the WebClient ([#1408](https://github.com/0xMiden/miden-client/pull/1408)).
1818
* Added `NoteFile` type, used for exporting and importing `Notes`([#1378](https://github.com/0xMiden/miden-client/pull/1383))
1919
* Build `IndexedDB` code from a `build.rs` instead of pushing artifacts to the repo ([#1409](https://github.com/0xMiden/miden-client/pull/1409)).
20+
* Implemented missing RPC endpoints: `/SyncStorageMaps`, `/SyncAccountVault` & `/SyncTransactions` ([#1362](https://github.com/0xMiden/miden-client/pull/1362)).
2021

2122
### Changes
2223

bin/integration-tests/src/tests/client.rs

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use std::sync::Arc;
22
use std::time::Duration;
33

44
use anyhow::{Context, Result};
5-
use miden_client::ClientError;
6-
use miden_client::account::{AccountId, AccountStorageMode};
5+
use miden_client::account::{AccountId, AccountStorageMode, StorageMap, StorageSlot};
6+
use miden_client::assembly::{DefaultSourceManager, LibraryPath, Module, ModuleKind};
77
use miden_client::asset::{Asset, FungibleAsset};
88
use miden_client::builder::ClientBuilder;
99
use miden_client::keystore::FilesystemKeyStore;
@@ -22,11 +22,13 @@ use miden_client::transaction::{
2222
PaymentNoteDescription,
2323
ProvenTransaction,
2424
TransactionInputs,
25+
TransactionKernel,
2526
TransactionProver,
2627
TransactionProverError,
2728
TransactionRequestBuilder,
2829
TransactionStatus,
2930
};
31+
use miden_client::{ClientError, Felt, ScriptBuilder};
3032
use miden_client_sqlite_store::ClientBuilderSqliteExt;
3133

3234
use crate::tests::config::ClientConfig;
@@ -1153,8 +1155,85 @@ pub async fn test_unused_rpc_api(client_config: ClientConfig) -> Result<()> {
11531155
consume_notes(&mut client, first_basic_account.id(), std::slice::from_ref(&note)).await;
11541156
wait_for_tx(&mut client, tx_id).await?;
11551157

1158+
// Define the account code for the custom library
1159+
let custom_code = "
1160+
use.miden::account
1161+
1162+
export.update_map
1163+
push.1.2.3.4
1164+
# => [VALUE]
1165+
push.0.0.0.0
1166+
# => [KEY, VALUE]
1167+
push.1
1168+
# => [index, KEY, VALUE]
1169+
exec.account::set_map_item
1170+
dropw dropw dropw dropw
1171+
end
1172+
";
1173+
1174+
let mut storage_map = StorageMap::new();
1175+
storage_map.insert(
1176+
[Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)].into(),
1177+
[Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(1)].into(),
1178+
)?;
1179+
1180+
let storage_slots = vec![StorageSlot::empty_map(), StorageSlot::Map(storage_map)];
1181+
let (account_with_map_item, _) = insert_account_with_custom_component(
1182+
&mut client,
1183+
custom_code,
1184+
storage_slots,
1185+
AccountStorageMode::Public,
1186+
&keystore,
1187+
)
1188+
.await?;
1189+
11561190
client.sync_state().await.unwrap();
11571191

1192+
let assembler = TransactionKernel::assembler();
1193+
let source_manager = Arc::new(DefaultSourceManager::default());
1194+
let module = Module::parser(ModuleKind::Library)
1195+
.parse_str(
1196+
LibraryPath::new("custom_library::set_map_item_library")
1197+
.context("failed to create library path for custom library")?,
1198+
custom_code,
1199+
&source_manager,
1200+
)
1201+
.unwrap();
1202+
let custom_lib = assembler.assemble_library([module]).unwrap();
1203+
1204+
let tx_script = ScriptBuilder::new(true)
1205+
.with_statically_linked_library(&custom_lib)?
1206+
.compile_tx_script(
1207+
"
1208+
use.custom_library::set_map_item_library
1209+
1210+
begin
1211+
call.set_map_item_library::update_map
1212+
end
1213+
",
1214+
)?;
1215+
1216+
let tx_request = TransactionRequestBuilder::new().custom_script(tx_script).build()?;
1217+
execute_tx_and_sync(&mut client, account_with_map_item.id(), tx_request.clone()).await?;
1218+
1219+
// Mint a new fungible asset to check account vault changes
1220+
let faucet = insert_new_fungible_faucet(&mut client, AccountStorageMode::Private, &keystore)
1221+
.await?
1222+
.0;
1223+
1224+
let fungible_asset = FungibleAsset::new(faucet.id(), MINT_AMOUNT)?;
1225+
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
1226+
fungible_asset,
1227+
first_basic_account.id(),
1228+
NoteType::Public,
1229+
client.rng(),
1230+
)?;
1231+
let note_id = tx_request.expected_output_own_notes().pop().unwrap().id();
1232+
execute_tx_and_sync(&mut client, fungible_asset.faucet_id(), tx_request.clone()).await?;
1233+
1234+
let tx_request = TransactionRequestBuilder::new().build_consume_notes(vec![note_id])?;
1235+
execute_tx_and_sync(&mut client, first_basic_account.id(), tx_request).await?;
1236+
11581237
let nullifier = note.nullifier();
11591238

11601239
let node_nullifier = client
@@ -1176,6 +1255,21 @@ pub async fn test_unused_rpc_api(client_config: ClientConfig) -> Result<()> {
11761255
.get_note_script_by_root(note.script().root())
11771256
.await
11781257
.unwrap();
1258+
let sync_storage_maps = client
1259+
.test_rpc_api()
1260+
.sync_storage_maps(0.into(), None, account_with_map_item.id())
1261+
.await
1262+
.unwrap();
1263+
let account_vault_info = client
1264+
.test_rpc_api()
1265+
.sync_account_vault(0.into(), None, first_basic_account.id())
1266+
.await
1267+
.unwrap();
1268+
let transactions_info = client
1269+
.test_rpc_api()
1270+
.sync_transactions(0.into(), None, vec![first_basic_account.id()])
1271+
.await
1272+
.unwrap();
11791273

11801274
// Remove debug decorators from original note script, as they are not persisted on submission
11811275
// https://github.com/0xMiden/miden-base/issues/1812
@@ -1186,6 +1280,9 @@ pub async fn test_unused_rpc_api(client_config: ClientConfig) -> Result<()> {
11861280
assert_eq!(node_nullifier.nullifier, nullifier);
11871281
assert_eq!(node_nullifier_proof.leaf().entries().first().unwrap().0, nullifier.as_word());
11881282
assert_eq!(note_script, retrieved_note_script);
1283+
assert!(!sync_storage_maps.updates.is_empty());
1284+
assert!(!account_vault_info.updates.is_empty());
1285+
assert!(!transactions_info.transaction_records.is_empty());
11891286

11901287
Ok(())
11911288
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use alloc::string::ToString;
2+
use alloc::vec::Vec;
3+
4+
use miden_objects::asset::Asset;
5+
use miden_objects::block::BlockNumber;
6+
use miden_objects::{AssetError, Word};
7+
8+
use crate::rpc::domain::MissingFieldHelper;
9+
use crate::rpc::{RpcError, generated as proto};
10+
11+
// ACCOUNT VAULT INFO
12+
// ================================================================================================
13+
14+
/// Represents a `proto::rpc_store::SyncAccountVaultResponse` with fields converted into domain
15+
/// types. Contains information of asset updates in a given range of blocks specified on request.
16+
/// Also provides the current chain tip while processing the request.
17+
pub struct AccountVaultInfo {
18+
/// Current chain tip
19+
pub chain_tip: BlockNumber,
20+
/// The block number of the last check included in this response.
21+
pub block_number: BlockNumber,
22+
/// List of asset updates for the account.
23+
pub updates: Vec<AccountVaultUpdate>,
24+
}
25+
26+
// ACCOUNT VAULT CONVERSION
27+
// ================================================================================================
28+
29+
impl TryFrom<proto::rpc_store::SyncAccountVaultResponse> for AccountVaultInfo {
30+
type Error = RpcError;
31+
32+
fn try_from(value: proto::rpc_store::SyncAccountVaultResponse) -> Result<Self, Self::Error> {
33+
let pagination_info = value.pagination_info.ok_or(
34+
proto::rpc_store::SyncAccountVaultResponse::missing_field(stringify!(pagination_info)),
35+
)?;
36+
let chain_tip = pagination_info.chain_tip;
37+
let block_number = pagination_info.block_num;
38+
39+
let updates = value
40+
.updates
41+
.iter()
42+
.map(|update| (*update).try_into())
43+
.collect::<Result<Vec<_>, _>>()?;
44+
45+
Ok(Self {
46+
chain_tip: chain_tip.into(),
47+
block_number: block_number.into(),
48+
updates,
49+
})
50+
}
51+
}
52+
53+
// ACCOUNT VAULT UPDATE
54+
// ================================================================================================
55+
56+
/// Represents an update to an account vault, including the vault key and asset value involved.
57+
pub struct AccountVaultUpdate {
58+
/// Block number in which the slot was updated.
59+
pub block_num: BlockNumber,
60+
/// Asset value related to the vault key. If not present, the asset was removed from the vault.
61+
pub asset: Option<Asset>,
62+
/// Vault key associated with the asset.
63+
pub vault_key: Word,
64+
}
65+
66+
// ACCOUNT VAULT UPDATE CONVERSION
67+
// ================================================================================================
68+
69+
impl TryFrom<proto::primitives::Asset> for Asset {
70+
type Error = RpcError;
71+
72+
fn try_from(value: proto::primitives::Asset) -> Result<Self, Self::Error> {
73+
let word: Word = value
74+
.asset
75+
.ok_or(proto::rpc_store::SyncAccountVaultResponse::missing_field(stringify!(asset)))?
76+
.try_into()?;
77+
word.try_into()
78+
.map_err(|e: AssetError| RpcError::InvalidResponse(e.to_string()))
79+
}
80+
}
81+
82+
impl TryFrom<proto::rpc_store::AccountVaultUpdate> for AccountVaultUpdate {
83+
type Error = RpcError;
84+
85+
fn try_from(value: proto::rpc_store::AccountVaultUpdate) -> Result<Self, Self::Error> {
86+
let block_num = value.block_num;
87+
88+
let asset: Option<Asset> = value.asset.map(TryInto::try_into).transpose()?;
89+
90+
let vault_key = value
91+
.vault_key
92+
.ok_or(proto::rpc_store::SyncAccountVaultResponse::missing_field(stringify!(
93+
vault_key
94+
)))?
95+
.try_into()?;
96+
97+
Ok(Self {
98+
block_num: block_num.into(),
99+
asset,
100+
vault_key,
101+
})
102+
}
103+
}

crates/rust-client/src/rpc/domain/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ use core::any::type_name;
33
use super::errors::RpcConversionError;
44

55
pub mod account;
6+
pub mod account_vault;
67
pub mod block;
78
pub mod digest;
89
pub mod merkle;
910
pub mod note;
1011
pub mod nullifier;
1112
pub mod smt;
13+
pub mod storage_map;
1214
pub mod sync;
1315
pub mod transaction;
1416

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use alloc::vec::Vec;
2+
3+
use miden_objects::Word;
4+
use miden_objects::block::BlockNumber;
5+
6+
use crate::rpc::domain::MissingFieldHelper;
7+
use crate::rpc::{RpcConversionError, RpcError, generated as proto};
8+
9+
// STORAGE MAP INFO
10+
// ================================================================================================
11+
12+
/// Represents a `proto::rpc_store::SyncStorageMapsResponse` with fields converted into domain
13+
/// types. Contains information of updated map slots in a given range of blocks specified on
14+
/// request. Also provides the current chain tip while processing the request.
15+
pub struct StorageMapInfo {
16+
/// Current chain tip
17+
pub chain_tip: BlockNumber,
18+
/// The block number of the last check included in this response.
19+
pub block_number: BlockNumber,
20+
/// The list of storage map updates.
21+
pub updates: Vec<StorageMapUpdate>,
22+
}
23+
24+
// STORAGE MAP INFO CONVERSION
25+
// ================================================================================================
26+
27+
impl TryFrom<proto::rpc_store::SyncStorageMapsResponse> for StorageMapInfo {
28+
type Error = RpcError;
29+
30+
fn try_from(value: proto::rpc_store::SyncStorageMapsResponse) -> Result<Self, Self::Error> {
31+
let pagination_info = value.pagination_info.ok_or(
32+
proto::rpc_store::SyncStorageMapsResponse::missing_field(stringify!(pagination_info)),
33+
)?;
34+
let chain_tip = pagination_info.chain_tip;
35+
let block_number = pagination_info.block_num;
36+
37+
let updates = value
38+
.updates
39+
.iter()
40+
.map(|update| (*update).try_into())
41+
.collect::<Result<Vec<_>, _>>()?;
42+
43+
Ok(Self {
44+
chain_tip: chain_tip.into(),
45+
block_number: block_number.into(),
46+
updates,
47+
})
48+
}
49+
}
50+
51+
// STORAGE MAP UPDATE
52+
// ================================================================================================
53+
54+
/// Represents a `proto::rpc_store::StorageMapUpdate`
55+
pub struct StorageMapUpdate {
56+
/// Block number in which the slot was updated.
57+
pub block_num: BlockNumber,
58+
/// Slot index ([0..255]).
59+
pub slot_index: u8,
60+
/// The storage map key
61+
pub key: Word,
62+
/// The storage map value.
63+
pub value: Word,
64+
}
65+
66+
// STORAGE MAP UPDATE CONVERSION
67+
// ================================================================================================
68+
69+
impl TryFrom<proto::rpc_store::StorageMapUpdate> for StorageMapUpdate {
70+
type Error = RpcConversionError;
71+
72+
fn try_from(value: proto::rpc_store::StorageMapUpdate) -> Result<Self, Self::Error> {
73+
let block_num = value.block_num;
74+
75+
let slot_index = value.slot_index;
76+
77+
let key: Word = value
78+
.key
79+
.ok_or(proto::rpc_store::StorageMapUpdate::missing_field(stringify!(key)))?
80+
.try_into()?;
81+
82+
let value: Word = value
83+
.value
84+
.ok_or(proto::rpc_store::StorageMapUpdate::missing_field(stringify!(value)))?
85+
.try_into()?;
86+
87+
Ok(Self {
88+
block_num: block_num.into(),
89+
slot_index: u8::try_from(slot_index)?,
90+
key,
91+
value,
92+
})
93+
}
94+
}

0 commit comments

Comments
 (0)