use std::net::IpAddr;
use std::sync::Arc;
use std::sync::Mutex;
use neptune_cash::api::export::BlockHeight;
use neptune_cash::api::export::ChangePolicy;
use neptune_cash::api::export::GenerationSpendingKey;
use neptune_cash::api::export::KeyType;
use neptune_cash::api::export::NativeCurrencyAmount;
use neptune_cash::api::export::Network;
use neptune_cash::api::export::OutputFormat;
use neptune_cash::api::export::ReceivingAddress;
use neptune_cash::api::export::SpendingKey;
use neptune_cash::api::export::SymmetricKey;
use neptune_cash::api::export::Timestamp;
use neptune_cash::api::export::TxCreationArtifacts;
use neptune_cash::application::rpc::auth;
use neptune_cash::application::rpc::server::error::RpcError;
use neptune_cash::application::rpc::server::mempool_transaction_info::MempoolTransactionInfo;
use neptune_cash::application::rpc::server::overview_data::OverviewData;
use neptune_cash::application::rpc::server::ui_utxo::UiUtxo;
use neptune_cash::application::rpc::server::RpcResult;
use neptune_cash::protocol::peer::peer_info::PeerInfo;
use neptune_cash::state::wallet::address::generation_address::GenerationReceivingAddress;
use rand::rng;
use rand::rngs::StdRng;
use rand::Rng;
use rand::SeedableRng;
use tasm_lib::prelude::Digest;
#[derive(Debug, Clone)]
pub(crate) struct MockRpcClient {
state: Arc<Mutex<MockState>>,
}
#[derive(Debug, Clone)]
struct MockState {
peers: Vec<PeerInfo>,
utxos: Vec<UiUtxo>,
known_keys: Vec<SpendingKey>,
history: Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)>,
mempool_transactions: Vec<MempoolTransactionInfo>,
overview_data: OverviewData,
generation_address: ReceivingAddress,
symmetric_address: ReceivingAddress,
}
impl MockRpcClient {
pub(crate) fn new() -> Self {
Self {
state: Arc::new(Mutex::new(Self::init_mock_data())),
}
}
fn init_mock_data() -> MockState {
let mut rng = StdRng::from_seed(rng().random());
let num_peers = 100;
let peers: Vec<PeerInfo> = (0..num_peers).map(|_| rng.random()).collect();
let num_utxos = rng.random_range(1..20);
let utxos: Vec<UiUtxo> = (0..num_utxos)
.map(|_| UiUtxo {
amount: rng
.random::<NativeCurrencyAmount>()
.lossy_f64_fraction_mul(0.0001),
release_date: if rng.random_bool(0.5) {
Some(rng.random())
} else {
None
},
received: rng.random(),
aocl_leaf_index: if rng.random_bool(0.5) {
Some(rng.random_range(0u64..(u64::MAX >> 20)))
} else {
None
},
spent: rng.random(),
})
.collect();
let num_keys = rng.random_range(1..100);
let known_keys: Vec<SpendingKey> = (0..num_keys)
.map(|_| match rng.random_range(0..2) {
0 => SpendingKey::from(GenerationSpendingKey::derive_from_seed(rng.random())),
1 => SpendingKey::from(SymmetricKey::from_seed(rng.random())),
_ => unreachable!(),
})
.collect();
let num_history = rng.random_range(0..100);
let history: Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)> = (0..num_history)
.map(|_| {
let digest = rng.random::<Digest>();
let block_height = rng.random::<BlockHeight>();
let timestamp = rng.random::<Timestamp>();
let native_currency_amount = rng
.random::<NativeCurrencyAmount>()
.lossy_f64_fraction_mul(0.0001);
(digest, block_height, timestamp, native_currency_amount)
})
.collect();
let total_num_entries = rng.random_range(5..100);
let mempool_transactions: Vec<MempoolTransactionInfo> =
(0..total_num_entries).map(|_| rng.random()).collect();
let overview_data: OverviewData = rng.random();
let generation_address =
ReceivingAddress::from(GenerationReceivingAddress::derive_from_seed(rng.random()));
let symmetric_address = ReceivingAddress::from(SymmetricKey::from_seed(rng.random()));
MockState {
peers,
utxos,
known_keys,
history,
mempool_transactions,
overview_data,
generation_address,
symmetric_address,
}
}
pub async fn network(
&self,
_ctx: ::tarpc::context::Context,
) -> ::core::result::Result<RpcResult<Network>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
Ok(Ok(Network::Main))
}
pub(crate) async fn dashboard_overview_data(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
) -> ::core::result::Result<RpcResult<OverviewData>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
Ok(Ok(state.overview_data.clone()))
}
pub async fn known_keys(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
) -> ::core::result::Result<RpcResult<Vec<SpendingKey>>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
Ok(Ok(state.known_keys.clone()))
}
pub async fn history(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
) -> ::core::result::Result<
RpcResult<Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)>>,
::tarpc::client::RpcError,
> {
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
Ok(Ok(state.history.clone()))
}
pub async fn mempool_overview(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
page_start: usize,
page_size: usize,
) -> ::core::result::Result<RpcResult<Vec<MempoolTransactionInfo>>, ::tarpc::client::RpcError>
{
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
let total_num_entries = state.mempool_transactions.len();
let range_start = usize::min(page_start * page_size, total_num_entries);
let range_stop = usize::min((page_start + 1) * page_size, total_num_entries);
let result = state.mempool_transactions[range_start..range_stop].to_vec();
Ok(Ok(result))
}
pub async fn peer_info(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
) -> ::core::result::Result<RpcResult<Vec<PeerInfo>>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
Ok(Ok(state.peers.clone()))
}
pub async fn latest_address(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
address_type: KeyType,
) -> ::core::result::Result<RpcResult<ReceivingAddress>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
let receiving_address = match address_type {
KeyType::Generation => state.generation_address.clone(),
KeyType::Symmetric => state.symmetric_address.clone(),
_ => todo!(),
};
Ok(Ok(receiving_address))
}
pub async fn next_receiving_address(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
address_type: KeyType,
) -> ::core::result::Result<RpcResult<ReceivingAddress>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let mut state = self.state.lock().unwrap();
let receiving_address = match address_type {
KeyType::Generation => {
state.generation_address =
GenerationReceivingAddress::derive_from_seed(rng().random()).into();
state.generation_address.clone()
}
KeyType::Symmetric => {
state.symmetric_address = neptune_cash::api::export::ReceivingAddress::Symmetric(
SymmetricKey::from_seed(rng().random()),
);
state.symmetric_address.clone()
}
_ => todo!(),
};
Ok(Ok(receiving_address))
}
pub async fn send(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
_outputs: Vec<OutputFormat>,
_change_policy: ChangePolicy,
fee: NativeCurrencyAmount,
accept_lustrations: bool,
) -> ::core::result::Result<RpcResult<TxCreationArtifacts>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let params = format!("fee: {fee}; accept_lustrations: {accept_lustrations}.");
Ok(Err(RpcError::Failed(format!(
"cannot send; mocking. Got: {params}",
))))
}
pub async fn list_utxos(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
) -> ::core::result::Result<RpcResult<Vec<UiUtxo>>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let state = self.state.lock().unwrap();
Ok(Ok(state.utxos.clone()))
}
pub async fn clear_standing_by_ip(
&self,
_ctx: ::tarpc::context::Context,
_token: auth::Token,
peer_ip: IpAddr,
) -> ::core::result::Result<RpcResult<()>, ::tarpc::client::RpcError> {
tokio::task::yield_now().await;
let mut state = self.state.lock().unwrap();
if let Some(idx) = state.peers.iter().position(|p| {
p.address()
.iter()
.find_map(|component| match component {
multiaddr::Protocol::Ip4(ip) => Some(IpAddr::V4(ip)),
multiaddr::Protocol::Ip6(ip) => Some(IpAddr::V6(ip)),
_ => None,
})
.is_some_and(|ip| ip == peer_ip)
}) {
let mut rng = StdRng::from_seed(rng().random());
state.peers[idx] = rng.random();
}
Ok(Ok(()))
}
}