pub mod chain_user;
use crate::simulator::chain_user::ChainUser;
use crate::wallets::memory_wallet::{MemoryWallet, MemoryWalletConfig, MemoryWalletStore};
use crate::wallets::{Wallet, WalletInfo};
use bip39::Mnemonic;
use dg_xch_clients::api::simulator::SimulatorAPI;
use dg_xch_clients::rpc::simulator::SimulatorClient;
use dg_xch_core::blockchain::sized_bytes::Bytes32;
use dg_xch_core::blockchain::wallet_type::WalletType;
use dg_xch_core::consensus::constants::{ConsensusConstants, SIMULATOR};
use dg_xch_keys::{decode_puzzle_hash, key_from_mnemonic};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::io::{Error, ErrorKind};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use tokio::time;
lazy_static! {
pub static ref UTIL_ADDRESS: Bytes32 =
decode_puzzle_hash("xch1ye5dzd44kkatnxx2je4s2agpwtqds5lsm5mlyef7plum5danxalq2dnqap")
.unwrap();
}
pub struct Simulator<'a> {
network: ConsensusConstants,
client: SimulatorClient,
run: Arc<AtomicBool>,
background: Mutex<Option<JoinHandle<()>>>,
users: Mutex<HashMap<String, Arc<ChainUser<'a>>>>,
}
impl<'a> Simulator<'a> {
pub fn new(
host: &str,
port: u16,
timeout: u64,
additional_headers: &Option<HashMap<String, String>>,
network: Option<ConsensusConstants>,
) -> Result<Self, Error> {
Ok(Self {
network: network.unwrap_or((**SIMULATOR).clone()),
client: SimulatorClient::new(host, port, timeout, additional_headers)?,
run: Arc::new(AtomicBool::new(false)),
background: Mutex::new(None),
users: Mutex::new(HashMap::default()),
})
}
pub fn client(&self) -> &SimulatorClient {
&self.client
}
pub fn constants(&self) -> &ConsensusConstants {
&self.network
}
pub async fn get_user(&self, name: &str) -> Option<Arc<ChainUser<'a>>> {
self.users.lock().await.get(name).cloned()
}
pub async fn new_user(
&'a self,
name: &str,
menmonic: Option<String>,
) -> Result<Arc<ChainUser<'a>>, Error> {
let mut map_lock = self.users.lock().await;
if map_lock.contains_key(name) {
return Err(Error::new(ErrorKind::AlreadyExists, "User already exists"));
}
let mnemonic = match menmonic {
Some(s) => Mnemonic::parse(s).map_err(Error::other)?,
None => Mnemonic::generate(24).map_err(Error::other)?,
};
let secret_key = key_from_mnemonic(&mnemonic).map_err(Error::other)?;
let chain_user = Arc::new(ChainUser {
simulator: self,
wallet: Arc::new(MemoryWallet::create_simulator(
WalletInfo {
id: 0,
name: format!("{name}'s Wallet"),
wallet_type: WalletType::StandardWallet,
constants: Arc::new(self.network.clone()),
master_sk: secret_key.clone(),
wallet_store: Arc::new(Mutex::new(MemoryWalletStore::new(secret_key, 0))),
data: String::new(),
},
MemoryWalletConfig {
fullnode_host: self.client.host.clone(),
fullnode_port: self.client.port,
fullnode_ssl_path: None,
additional_headers: self.client.additional_headers.clone(),
},
)?),
name: name.to_string(),
});
map_lock.insert(name.to_string(), chain_user.clone());
Ok(chain_user)
}
pub async fn next_blocks(&self, blocks: i64, call_per_block: bool) -> Result<(), Error> {
if call_per_block {
for _ in 0..blocks {
self.client.farm_blocks(*UTIL_ADDRESS, 1, true).await?;
}
} else {
self.client.farm_blocks(*UTIL_ADDRESS, blocks, true).await?;
}
Ok(())
}
pub async fn farm_coins(
&self,
address: Bytes32,
blocks: i64,
transaction_block: bool,
) -> Result<(), Error> {
self.client
.farm_blocks(address, blocks, transaction_block)
.await
.map_err(Into::into)
.map(|_| ())
}
pub async fn is_auto_farming(&self) -> Result<bool, Error> {
self.client
.get_auto_farming()
.await
.map_err(Into::into)
.map(|r| r.auto_farm_enabled)
}
pub async fn run(&self, block_interval: Option<Duration>) -> Result<(), Error> {
let mut block_interval = time::interval(block_interval.unwrap_or(Duration::from_secs(19)));
let run = self.run.clone();
if let Some(background) = &mut *self.background.lock().await {
if run.load(Ordering::Relaxed) {
return Err(Error::new(
ErrorKind::AlreadyExists,
"Simulator Already Running",
));
}
run.store(false, Ordering::Relaxed);
background.await?;
}
run.store(true, Ordering::Relaxed);
*self.background.lock().await = Some(tokio::spawn(async move {
while run.load(Ordering::Relaxed) {
block_interval.tick().await;
}
}));
Ok(())
}
pub async fn stop(&self) -> Result<(), Error> {
self.run.store(false, Ordering::Relaxed);
if let Some(background) = &mut *self.background.lock().await {
background.await?;
Ok(())
} else {
Err(Error::new(
ErrorKind::AlreadyExists,
"Simulator Not Running",
))
}
}
}