From 098ac562baa4201257361c22d238e78a40707dd1 Mon Sep 17 00:00:00 2001 From: qima Date: Fri, 14 Jun 2024 16:08:29 +0800 Subject: [PATCH 1/3] test(wallet): recent payment selective --- sn_transfers/src/wallet/api.rs | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/sn_transfers/src/wallet/api.rs b/sn_transfers/src/wallet/api.rs index b37ff6a0a0..6ae684d00f 100644 --- a/sn_transfers/src/wallet/api.rs +++ b/sn_transfers/src/wallet/api.rs @@ -120,3 +120,49 @@ impl WalletApi { Ok(payments) } } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{MainSecretKey, NanoTokens, PaymentQuote, Transfer}; + + #[test] + fn payment_selective() -> Result<()> { + let root_dir = std::env::temp_dir(); + let wallet_api = WalletApi::new_from_wallet_dir(&root_dir); + + let mut rng = bls::rand::thread_rng(); + let chunk_name = XorName::random(&mut rng); + + let transfer = Transfer::NetworkRoyalties(vec![]); + + let recipient_1 = MainSecretKey::random().main_pubkey(); + let payment_details_1 = PaymentDetails { + recipient: recipient_1, + peer_id_bytes: vec![], + transfer: (transfer.clone(), NanoTokens::zero()), + royalties: (transfer.clone(), NanoTokens::zero()), + quote: PaymentQuote::zero(), + }; + let _ = wallet_api.insert_payment_transaction(chunk_name, payment_details_1); + + let recipient_2 = MainSecretKey::random().main_pubkey(); + let payment_details_2 = PaymentDetails { + recipient: recipient_2, + peer_id_bytes: vec![], + transfer: (transfer.clone(), NanoTokens::zero()), + royalties: (transfer, NanoTokens::zero()), + quote: PaymentQuote::zero(), + }; + let _ = wallet_api.insert_payment_transaction(chunk_name, payment_details_2.clone()); + + let recent_payment = wallet_api.get_recent_payment(&chunk_name)?; + assert_eq!(payment_details_2.recipient, recent_payment.recipient); + + let recent_payment = wallet_api.get_recent_payment(&chunk_name)?; + assert_eq!(payment_details_2.recipient, recent_payment.recipient); + + Ok(()) + } +} From 4cb570a04cfb30d87cc4cf7daab7692ad33cff04 Mon Sep 17 00:00:00 2001 From: qima Date: Fri, 14 Jun 2024 16:09:10 +0800 Subject: [PATCH 2/3] chore(client): no extra get retry when there is put retry --- sn_client/src/api.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sn_client/src/api.rs b/sn_client/src/api.rs index 1e050ee2e1..6359147e57 100644 --- a/sn_client/src/api.rs +++ b/sn_client/src/api.rs @@ -863,8 +863,9 @@ impl Client { let cash_note_addr = SpendAddress::from_unique_pubkey(&unique_pubkey); let network_address = NetworkAddress::from_spend_address(cash_note_addr); - trace!("Sending spend {unique_pubkey:?} to the network via put_record, with addr of {cash_note_addr:?}"); let key = network_address.to_record_key(); + let pretty_key = PrettyPrintRecordKey::from(&key); + trace!("Sending spend {unique_pubkey:?} to the network via put_record, with addr of {cash_note_addr:?} - {pretty_key:?}"); let record_kind = RecordKind::Spend; let record = Record { key, @@ -886,9 +887,10 @@ impl Client { (None, Default::default()) }; + // When there is retry on Put side, no need to have a retry on Get let verification_cfg = GetRecordCfg { get_quorum: Quorum::Majority, - retry_strategy: Some(RetryStrategy::Balanced), + retry_strategy: None, target_record: record_to_verify, expected_holders, }; From 29c594092a952beebda664845ea881d6caa4c082 Mon Sep 17 00:00:00 2001 From: qima Date: Fri, 14 Jun 2024 16:36:15 +0800 Subject: [PATCH 3/3] feat(uploader): block uploader when there is unconfirmed spend --- sn_client/src/api.rs | 28 ++++++++++------- sn_client/src/uploader/upload.rs | 4 +++ sn_client/src/wallet.rs | 53 ++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/sn_client/src/api.rs b/sn_client/src/api.rs index 6359147e57..0d78dea614 100644 --- a/sn_client/src/api.rs +++ b/sn_client/src/api.rs @@ -945,13 +945,14 @@ impl Client { .await } - /// This is a similar funcation to `get_spend_from_network` to get a spend from network. - /// Just using different `RetryStrategy` to improve the performance during crawling. - pub async fn crawl_spend_from_network(&self, address: SpendAddress) -> Result { + /// Try to peek a spend by just fetching one copy of it. + /// Useful to help decide whether a re-put is necessary, or a spend exists already + /// (client side verification). + pub async fn peek_a_spend(&self, address: SpendAddress) -> Result { self.try_fetch_spend_from_network( address, GetRecordCfg { - get_quorum: Quorum::Majority, + get_quorum: Quorum::One, retry_strategy: None, target_record: None, expected_holders: Default::default(), @@ -960,21 +961,26 @@ impl Client { .await } - /// Try to confirm the Genesis spend doesn't present in the network yet. - /// It shall be quick, and any signle returned copy shall consider as error. - pub async fn is_genesis_spend_present(&self) -> bool { - let genesis_addr = SpendAddress::from_unique_pubkey(&GENESIS_SPEND_UNIQUE_KEY); + /// This is a similar funcation to `get_spend_from_network` to get a spend from network. + /// Just using different `RetryStrategy` to improve the performance during crawling. + pub async fn crawl_spend_from_network(&self, address: SpendAddress) -> Result { self.try_fetch_spend_from_network( - genesis_addr, + address, GetRecordCfg { - get_quorum: Quorum::One, + get_quorum: Quorum::Majority, retry_strategy: None, target_record: None, expected_holders: Default::default(), }, ) .await - .is_ok() + } + + /// Try to confirm the Genesis spend doesn't present in the network yet. + /// It shall be quick, and any signle returned copy shall consider as error. + pub async fn is_genesis_spend_present(&self) -> bool { + let genesis_addr = SpendAddress::from_unique_pubkey(&GENESIS_SPEND_UNIQUE_KEY); + self.peek_a_spend(genesis_addr).await.is_ok() } async fn try_fetch_spend_from_network( diff --git a/sn_client/src/uploader/upload.rs b/sn_client/src/uploader/upload.rs index 67bfe06d8e..c3f88d2c80 100644 --- a/sn_client/src/uploader/upload.rs +++ b/sn_client/src/uploader/upload.rs @@ -860,6 +860,10 @@ impl InnerUploader { got_a_previous_force_payment = false; } + let _ = wallet_client + .resend_pending_transaction_blocking_loop() + .await; + let result = match wallet_client.pay_for_records(&cost_map, verify_store).await { Ok((storage_cost, royalty_fees)) => { diff --git a/sn_client/src/wallet.rs b/sn_client/src/wallet.rs index eb9c9f7108..ef48bde427 100644 --- a/sn_client/src/wallet.rs +++ b/sn_client/src/wallet.rs @@ -669,6 +669,59 @@ impl WalletClient { } } + /// This is a blocking loop in cas there is pending transaction. + /// It will keeps resending the unconfirmed spend infinitely but explictly. + /// Function will only return on success (all unconfirmed spend uploaded), + /// or user chose to manualy, but safely, terminate the procedure. + pub async fn resend_pending_transaction_blocking_loop(&mut self) -> WalletResult<()> { + if !self.wallet.unconfirmed_spend_requests_exist() { + return Ok(()); + } + // Wallet shall be all clear to progress forward. + while self.wallet.unconfirmed_spend_requests_exist() { + info!("Pre-Unconfirmed transactions dected, sending again after 30 seconds..."); + println!("Pre-Unconfirmed transactions exist, sending again after 30 seconds..."); + println!("It's safe to terminate the work, but do remember to retain the unconfirmed_spend file during wallet update."); + println!("Otherwise, you are in risk to make the wallet corrupted."); + // Longer wait as the network will already in heavy duty situation, + // hence try not to give it further burden with short intervaled re-puts. + sleep(Duration::from_secs(30)).await; + + // Before re-sending, take a peek of un-confirmed spends first + // Helping user having a better view of what's happening. + let unconfirmed_spends_addrs: Vec<_> = self + .wallet + .unconfirmed_spend_requests() + .iter() + .map(|s| SpendAddress::from_unique_pubkey(&s.spend.unique_pubkey)) + .collect(); + for addr in unconfirmed_spends_addrs { + match self.client.peek_a_spend(addr).await { + Ok(_) => { + info!("Unconfirmed Spend {addr:?} is find having at least one copy in the network !"); + println!( + "Unconfirmed Spend {addr:?} is find at least one copy in the network !" + ); + } + Err(err) => { + info!( + "Unconfirmed Spend {addr:?} has no copy in the network yet {err:?} !" + ); + println!( + "Unconfirmed Spend {addr:?} has no copy in the network yet {err:?} !" + ); + } + } + } + + self.resend_pending_transactions(true).await; + } + info!("Wallet is now all cleared, OK to progress further."); + println!("Wallet is now all cleared, OK to progress further."); + eprintln!("WARNING: Closing the client now could corrupt the wallet !"); + Ok(()) + } + /// Try resending failed transactions multiple times until it succeeds or until we reach max attempts. async fn resend_pending_transaction_until_success( &mut self,