Skip to content
Open
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
106 changes: 102 additions & 4 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,7 @@ chrono = { workspace = true }
rand = { workspace = true }

[dev-dependencies]
serial_test = { workspace = true }
miden-lib = { default-features = false, git = "https://github.com/0xMiden/miden-base.git", rev = "e5ae2329735d2a89b6589375ca7fd91e4f104422" }
miden-note-transport-node = { features = ["testing"], workspace = true }
miden-testing = { default-features = false, git = "https://github.com/0xMiden/miden-base.git", rev = "e5ae2329735d2a89b6589375ca7fd91e4f104422" }
serial_test = { workspace = true }
117 changes: 117 additions & 0 deletions crates/node/tests/common/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! Mock client

use std::time::Duration;

use anyhow::Result;
use miden_lib::account::wallets::BasicWallet;
use miden_note_transport_proto::miden_note_transport::miden_note_transport_client::MidenNoteTransportClient;
use miden_note_transport_proto::miden_note_transport::{
FetchNotesRequest,
SendNoteRequest,
TransportNote,
};
use miden_objects::account::{Account, AccountBuilder, AccountStorageMode};
use miden_objects::address::{Address, AddressInterface, RoutingParameters};
use miden_objects::crypto::dsa::eddsa_25519::SecretKey;
use miden_objects::crypto::ies::{SealedMessage, SealingKey, UnsealingKey};
use miden_objects::note::{Note, NoteDetails, NoteHeader, NoteTag};
use miden_objects::utils::{Deserializable, Serializable};
use miden_testing::Auth;
use rand::Rng;
use tonic::Request;
use tonic::transport::Channel;

pub struct Client {
pub grpc: MidenNoteTransportClient<Channel>,
pub unsealing_key: UnsealingKey,
pub address: Address,
}

impl Client {
pub async fn init(endpoint: String, timeout_ms: u64) -> Result<Self> {
let channel = tonic::transport::Endpoint::try_from(endpoint)?
.timeout(Duration::from_millis(timeout_ms))
.connect()
.await?;
let grpc = MidenNoteTransportClient::new(channel);

let account = mock_account();

let secret_key = SecretKey::with_rng(&mut rand::rng());

let public_key = secret_key.public_key();
let encryption_key = SealingKey::X25519XChaCha20Poly1305(public_key);
let address = Address::new(account.id())
.with_routing_parameters(
RoutingParameters::new(AddressInterface::BasicWallet)
.with_encryption_key(encryption_key),
)
.unwrap();

let unsealing_key = UnsealingKey::X25519XChaCha20Poly1305(secret_key);

Ok(Self { grpc, unsealing_key, address })
}

pub async fn send_note(&mut self, note: Note, address: &Address) -> Result<()> {
let header = *note.header();
let details: NoteDetails = note.into();

let details_enc = address
.encryption_key()
.unwrap()
.seal_bytes(&mut rand::rng(), &details.to_bytes())
.unwrap();

let request = SendNoteRequest {
note: Some(TransportNote {
header: header.to_bytes(),
details: details_enc.to_bytes(),
}),
};

self.grpc.send_note(Request::new(request)).await?;

Ok(())
}

pub async fn fetch_notes(&mut self, tags: &[NoteTag], cursor: u64) -> Result<(Vec<Note>, u64)> {
let tags_int = tags.iter().map(NoteTag::as_u32).collect();
let request = FetchNotesRequest { tags: tags_int, cursor };

let response = self.grpc.fetch_notes(Request::new(request)).await?;

let response = response.into_inner();

let mut notes = Vec::new();

for pnote in response.notes {
let sealed_msg = SealedMessage::read_from_bytes(&pnote.details)?;
// try decrypt, if fail just ignore
let Ok(details_bytes) = self.unsealing_key.unseal_bytes(sealed_msg) else {
continue;
};
let details = NoteDetails::read_from_bytes(&details_bytes)?;
let header = NoteHeader::read_from_bytes(&pnote.header)?;

let note = Note::new(
details.assets().clone(),
*header.metadata(),
details.recipient().clone(),
);
notes.push(note);
}

Ok((notes, response.cursor))
}
}

pub fn mock_account() -> Account {
let mut rng = rand::rng();
AccountBuilder::new(rng.random())
.storage_mode(AccountStorageMode::Private)
.with_component(BasicWallet)
.with_auth_component(Auth::BasicAuth)
.build()
.unwrap()
}
52 changes: 52 additions & 0 deletions crates/node/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
mod client;

use miden_lib::note::create_p2id_note;
use miden_note_transport_node::node::grpc::GrpcServerConfig;
use miden_note_transport_node::{Node, NodeConfig};
use miden_objects::Felt;
use miden_objects::account::AccountId;
use miden_objects::address::{Address, AddressId};
use miden_objects::crypto::rand::RpoRandomCoin;
use miden_objects::note::{Note, NoteType};
use rand::RngCore;
use tokio::task::JoinHandle;
use tokio::time::{Duration, sleep};

use self::client::Client;

pub async fn spawn_test_server(port: u16) -> JoinHandle<()> {
let config = NodeConfig {
grpc: GrpcServerConfig { port, ..Default::default() },
..Default::default()
};

let server = Node::init(config).await.unwrap();
let handle = tokio::spawn(server.entrypoint());
// Wait for startup
sleep(Duration::from_millis(100)).await;
handle
}

pub async fn mock_client(port: u16) -> Client {
let timeout_ms = 1000;
let url = format!("http://127.0.0.1:{port}");
Client::init(url, timeout_ms).await.unwrap()
}

pub fn mock_note_p2id_with_addresses(sender: &Address, target: &Address) -> Note {
let mut randrng = rand::rng();
let seed: [Felt; 4] = core::array::from_fn(|_| Felt::new(randrng.next_u64()));
let mut rng = RpoRandomCoin::new(seed.into());
let sender_id = addrid_to_accid(&sender.id());
let target_id = addrid_to_accid(&target.id());
create_p2id_note(sender_id, target_id, vec![], NoteType::Private, Felt::default(), &mut rng)
.unwrap()
}

pub fn addrid_to_accid(addrid: &AddressId) -> AccountId {
if let AddressId::AccountId(accid) = addrid {
*accid
} else {
panic!()
}
}
Loading