From 478bb9c849cd927b53f5c1f12bd57e0ddf5c0020 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 12 Nov 2024 11:13:54 -0500 Subject: [PATCH] Initial test framework --- .gitignore | 2 +- Cargo.lock | 66 ++++++--- Cargo.toml | 5 +- crates/sage-wallet/Cargo.toml | 7 +- crates/sage-wallet/src/lib.rs | 6 + crates/sage-wallet/src/queues/puzzle_queue.rs | 35 +++-- .../src/sync_manager/peer_discovery.rs | 6 +- crates/sage-wallet/src/test.rs | 125 ++++++++++++++++++ crates/sage-wallet/src/wallet/p2_send.rs | 54 ++++++++ src-tauri/Cargo.toml | 2 +- 10 files changed, 263 insertions(+), 45 deletions(-) create mode 100644 crates/sage-wallet/src/test.rs diff --git a/.gitignore b/.gitignore index 54a00fd6..2058a331 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,6 @@ dist-ssr *.sw? # Rust -/target +target /.env /test.sqlite* diff --git a/Cargo.lock b/Cargo.lock index f34e91b5..6a58692d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -752,8 +752,8 @@ dependencies = [ [[package]] name = "chia-sdk-client" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "aws-lc-rs", "chia-protocol", @@ -773,8 +773,8 @@ dependencies = [ [[package]] name = "chia-sdk-derive" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "convert_case 0.6.0", "quote", @@ -783,8 +783,8 @@ dependencies = [ [[package]] name = "chia-sdk-driver" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "bigdecimal", "chia-bls 0.15.0", @@ -802,8 +802,8 @@ dependencies = [ [[package]] name = "chia-sdk-offers" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "bech32", "chia-bls 0.15.0", @@ -823,8 +823,8 @@ dependencies = [ [[package]] name = "chia-sdk-signer" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "chia-bls 0.15.0", "chia-consensus", @@ -837,8 +837,8 @@ dependencies = [ [[package]] name = "chia-sdk-test" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "anyhow", "bip39", @@ -869,8 +869,8 @@ dependencies = [ [[package]] name = "chia-sdk-types" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "chia-bls 0.15.0", "chia-consensus", @@ -884,8 +884,8 @@ dependencies = [ [[package]] name = "chia-sdk-utils" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "bech32", "chia-protocol", @@ -943,8 +943,8 @@ dependencies = [ [[package]] name = "chia-wallet-sdk" -version = "0.18.0" -source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=543743574fc15662395dc5d15b1cbf250783f802#543743574fc15662395dc5d15b1cbf250783f802" +version = "0.19.0" +source = "git+https://github.com/xch-dev/chia-wallet-sdk?rev=c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b#c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" dependencies = [ "chia-sdk-client", "chia-sdk-driver", @@ -2821,7 +2821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4481,6 +4481,7 @@ dependencies = [ name = "sage-wallet" version = "0.7.0" dependencies = [ + "anyhow", "chia", "chia-wallet-sdk", "clvmr", @@ -4493,6 +4494,8 @@ dependencies = [ "sage-database", "serde", "serde_json", + "sqlx", + "test-log", "thiserror 1.0.63", "tokio", "tracing", @@ -5643,6 +5646,27 @@ dependencies = [ "utf-8", ] +[[package]] +name = "test-log" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" +dependencies = [ + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thin-slice" version = "0.1.1" @@ -6137,9 +6161,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index 89c07c71..5f7a1d00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ tauri-specta = "2.0.0-rc.20" # Chia chia = "0.15.0" clvmr = "0.9.0" -chia-wallet-sdk = { version = "0.18.0", features = ["rustls"] } +chia-wallet-sdk = { version = "0.19.0", features = ["rustls"] } bip39 = "2.0.0" bech32 = "0.9.1" @@ -105,6 +105,7 @@ tracing = "0.1.40" tracing-subscriber = "0.3.18" tracing-appender = "0.2.3" log = "0.4.22" +test-log = { version = "0.2.16", default-features = false } [patch.crates-io] -chia-wallet-sdk = { git = "https://github.com/xch-dev/chia-wallet-sdk", rev = "543743574fc15662395dc5d15b1cbf250783f802" } +chia-wallet-sdk = { git = "https://github.com/xch-dev/chia-wallet-sdk", rev = "c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" } diff --git a/crates/sage-wallet/Cargo.toml b/crates/sage-wallet/Cargo.toml index 72eea27b..bb8e8da2 100644 --- a/crates/sage-wallet/Cargo.toml +++ b/crates/sage-wallet/Cargo.toml @@ -26,7 +26,12 @@ itertools = { workspace = true } futures-util = { workspace = true } futures-lite = { workspace = true } rayon = { workspace = true } -reqwest = { workspace = true, default-features = false, features = ["http2", "rustls-tls-webpki-roots"] } +reqwest = { workspace = true, default-features = false, features = ["http2", "rustls-tls-webpki-roots", "json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } indexmap = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +sqlx = { workspace = true, features = ["runtime-tokio"] } +test-log = { workspace = true, features = ["trace"] } diff --git a/crates/sage-wallet/src/lib.rs b/crates/sage-wallet/src/lib.rs index adb24b19..ba3c2ccb 100644 --- a/crates/sage-wallet/src/lib.rs +++ b/crates/sage-wallet/src/lib.rs @@ -19,3 +19,9 @@ pub use transaction::*; pub use utils::*; pub use wallet::*; pub use wallet_peer::*; + +#[cfg(test)] +mod test; + +#[cfg(test)] +pub use test::*; diff --git a/crates/sage-wallet/src/queues/puzzle_queue.rs b/crates/sage-wallet/src/queues/puzzle_queue.rs index 1480c100..265c0089 100644 --- a/crates/sage-wallet/src/queues/puzzle_queue.rs +++ b/crates/sage-wallet/src/queues/puzzle_queue.rs @@ -43,37 +43,24 @@ impl PuzzleQueue { pub async fn start(mut self) -> Result<(), WalletError> { loop { - let subscriptions = self.process_batch().await?; - - self.command_sender - .send(SyncCommand::SubscribeCoins { - coin_ids: subscriptions, - }) - .await - .ok(); - - self.sync_sender - .send(SyncEvent::PuzzleBatchSynced) - .await - .ok(); - + self.process_batch().await?; sleep(Duration::from_millis(150)).await; } } - async fn process_batch(&mut self) -> Result, WalletError> { + async fn process_batch(&mut self) -> Result<(), WalletError> { let peers = self.state.lock().await.peers(); if peers.is_empty() { sleep(Duration::from_secs(3)).await; - return Ok(Vec::new()); + return Ok(()); } let coin_states = self.db.unsynced_coin_states(peers.len()).await?; if coin_states.is_empty() { sleep(Duration::from_secs(3)).await; - return Ok(Vec::new()); + return Ok(()); } debug!( @@ -120,7 +107,19 @@ impl PuzzleQueue { } } - Ok(subscriptions) + self.command_sender + .send(SyncCommand::SubscribeCoins { + coin_ids: subscriptions, + }) + .await + .ok(); + + self.sync_sender + .send(SyncEvent::PuzzleBatchSynced) + .await + .ok(); + + Ok(()) } } diff --git a/crates/sage-wallet/src/sync_manager/peer_discovery.rs b/crates/sage-wallet/src/sync_manager/peer_discovery.rs index bb86f382..ce495e8d 100644 --- a/crates/sage-wallet/src/sync_manager/peer_discovery.rs +++ b/crates/sage-wallet/src/sync_manager/peer_discovery.rs @@ -203,7 +203,11 @@ impl SyncManager { self.state.lock().await.peer_count() >= self.options.target_peers } - async fn try_add_peer(&mut self, peer: Peer, mut receiver: mpsc::Receiver) -> bool { + pub(crate) async fn try_add_peer( + &mut self, + peer: Peer, + mut receiver: mpsc::Receiver, + ) -> bool { let Ok(Some(message)) = timeout(self.options.timeouts.initial_peak, receiver.recv()).await else { debug!( diff --git a/crates/sage-wallet/src/test.rs b/crates/sage-wallet/src/test.rs new file mode 100644 index 00000000..ca6c6a09 --- /dev/null +++ b/crates/sage-wallet/src/test.rs @@ -0,0 +1,125 @@ +use std::{sync::Arc, time::Duration}; + +use chia::{ + bls::{master_to_wallet_unhardened_intermediate, DerivableKey, SecretKey}, + protocol::{Bytes32, CoinSpend}, + puzzles::{standard::StandardArgs, DeriveSynthetic}, +}; +use chia_wallet_sdk::{ + test_secret_key, AggSigConstants, Connector, Network, Peer, PeerSimulator, TESTNET11_CONSTANTS, +}; +use sage_database::Database; +use sqlx::{migrate, SqlitePool}; +use tokio::{ + sync::{ + mpsc::{Receiver, Sender}, + Mutex, + }, + time::timeout, +}; + +use crate::{PeerState, SyncCommand, SyncEvent, SyncManager, SyncOptions, Timeouts, Wallet}; + +#[derive(Debug)] +pub struct TestWallet { + pub sim: PeerSimulator, + pub peer: Peer, + pub wallet: Arc, + pub master_sk: SecretKey, + pub puzzle_hash: Bytes32, + pub sender: Sender, + pub events: Receiver, +} + +impl TestWallet { + pub async fn new(pool: SqlitePool, balance: u64) -> anyhow::Result { + migrate!("../../migrations").run(&pool).await?; + let db = Database::new(pool); + + let sk = test_secret_key()?; + let pk = sk.public_key(); + let fingerprint = pk.get_fingerprint(); + let intermediate_pk = master_to_wallet_unhardened_intermediate(&pk); + let genesis_challenge = TESTNET11_CONSTANTS.genesis_challenge; + + let sim = PeerSimulator::new().await?; + let puzzle_hash = + StandardArgs::curry_tree_hash(intermediate_pk.derive_unhardened(0).derive_synthetic()); + + if balance > 0 { + sim.mint_coin(puzzle_hash.into(), balance).await; + } + + let state = Arc::new(Mutex::new(PeerState::default())); + let wallet = Arc::new(Wallet::new( + db, + fingerprint, + intermediate_pk, + genesis_challenge, + )); + + let (mut sync_manager, sender, events) = SyncManager::new( + SyncOptions { + target_peers: 0, + dns_batch_size: 0, + connection_batch_size: 0, + max_peer_age_seconds: 0, + sync_delay: Duration::from_millis(250), + timeouts: Timeouts::default(), + }, + state, + Some(wallet.clone()), + "testnet11".to_string(), + Network::default_testnet11(), + Connector::Plain, + ); + + let (peer, receiver) = sim.connect_raw().await?; + + assert!(sync_manager.try_add_peer(peer.clone(), receiver).await); + + tokio::spawn(sync_manager.sync()); + + let mut test = TestWallet { + sim, + peer, + wallet, + master_sk: sk, + puzzle_hash: puzzle_hash.into(), + sender, + events, + }; + + test.consume_until(SyncEvent::Subscribed).await; + + Ok(test) + } + + pub async fn transact(&self, coin_spends: Vec) -> anyhow::Result<()> { + let spend_bundle = self + .wallet + .sign_transaction( + coin_spends, + &AggSigConstants::new(TESTNET11_CONSTANTS.agg_sig_me_additional_data), + self.master_sk.clone(), + ) + .await?; + + self.peer.send_transaction(spend_bundle).await?; + + Ok(()) + } + + pub async fn consume_until(&mut self, event: SyncEvent) { + loop { + if event + == timeout(Duration::from_secs(10), self.events.recv()) + .await + .expect("timed out listening for events") + .expect("missing event") + { + return; + } + } + } +} diff --git a/crates/sage-wallet/src/wallet/p2_send.rs b/crates/sage-wallet/src/wallet/p2_send.rs index 3939ddde..a1fc5ef6 100644 --- a/crates/sage-wallet/src/wallet/p2_send.rs +++ b/crates/sage-wallet/src/wallet/p2_send.rs @@ -43,3 +43,57 @@ impl Wallet { Ok(ctx.take()) } } + +#[cfg(test)] +mod tests { + use sqlx::SqlitePool; + use test_log::test; + + use crate::{SyncEvent, TestWallet}; + + #[test(sqlx::test)] + async fn test_send_xch(pool: SqlitePool) -> anyhow::Result<()> { + let mut test = TestWallet::new(pool, 1000).await?; + + assert_eq!(test.wallet.db.balance().await?, 1000); + assert_eq!(test.wallet.db.spendable_coins().await?.len(), 1); + + let coin_spends = test + .wallet + .send_xch(test.puzzle_hash, 1000, 0, Vec::new(), false, true) + .await?; + + assert_eq!(coin_spends.len(), 1); + + test.transact(coin_spends).await?; + test.consume_until(SyncEvent::CoinState).await; + + assert_eq!(test.wallet.db.balance().await?, 1000); + assert_eq!(test.wallet.db.spendable_coins().await?.len(), 1); + + Ok(()) + } + + #[test(sqlx::test)] + async fn test_send_xch_change(pool: SqlitePool) -> anyhow::Result<()> { + let mut test = TestWallet::new(pool, 1000).await?; + + assert_eq!(test.wallet.db.balance().await?, 1000); + assert_eq!(test.wallet.db.spendable_coins().await?.len(), 1); + + let coin_spends = test + .wallet + .send_xch(test.puzzle_hash, 250, 250, Vec::new(), false, true) + .await?; + + assert_eq!(coin_spends.len(), 1); + + test.transact(coin_spends).await?; + test.consume_until(SyncEvent::CoinState).await; + + assert_eq!(test.wallet.db.balance().await?, 750); + assert_eq!(test.wallet.db.spendable_coins().await?.len(), 2); + + Ok(()) + } +} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c7a6755b..32ee2960 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -61,4 +61,4 @@ tauri-build = { workspace = true, features = [] } ignored = ["serde_json", "aws-lc-rs"] [patch.crates-io] -chia-wallet-sdk = { git = "https://github.com/xch-dev/chia-wallet-sdk", rev = "543743574fc15662395dc5d15b1cbf250783f802" } +chia-wallet-sdk = { git = "https://github.com/xch-dev/chia-wallet-sdk", rev = "c9f0c19f9ca7cd8ceb8b787e9ad15d8a1247da6b" }