Skip to content

Commit

Permalink
feat(transfers): enable client to check if a quote has expired
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandSherwin committed Mar 25, 2024
1 parent 5c26f7e commit f594eba
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 66 deletions.
2 changes: 1 addition & 1 deletion sn_client/src/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl FilesApi {
trace!("Client upload started for chunk: {chunk_addr:?}");

let wallet_client = self.wallet()?;
let (payment, payee) = wallet_client.get_recent_payment_for_addr(&chunk_addr)?;
let (payment, payee) = wallet_client.get_non_expired_payment_for_addr(&chunk_addr)?;

debug!(
"{:?} payments for chunk: {chunk_addr:?} to {payee:?}: {payment:?}",
Expand Down
2 changes: 1 addition & 1 deletion sn_client/src/folders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl FoldersApi {
}
}

let payment_info = wallet_client.get_recent_payment_for_addr(&self.as_net_addr())?;
let payment_info = wallet_client.get_non_expired_payment_for_addr(&self.as_net_addr())?;

self.register
.sync(&mut wallet_client, verify_store, Some(payment_info))
Expand Down
3 changes: 2 additions & 1 deletion sn_client/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@ impl ClientRegister {
royalties_fees = payment_result.royalty_fees;

// Get payment proofs needed to publish the Register
let (payment, payee) = wallet_client.get_recent_payment_for_addr(&net_addr)?;
let (payment, payee) =
wallet_client.get_non_expired_payment_for_addr(&net_addr)?;
debug!("payments found: {payment:?}");
payment_info = Some((payment, payee));
}
Expand Down
228 changes: 183 additions & 45 deletions sn_client/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ impl WalletClient {
self.wallet.unconfirmed_spend_requests()
}

/// Returns the Cached Payment for a provided NetworkAddress.
/// Returns the most recent cached Payment for a provided NetworkAddress. This function does not check if the
/// quote has expired or not. Use get_non_expired_payment_for_addr if you want to get a non expired one.
///
/// If multiple payments have been made to the same address, then we pick the last one as it is the most recent.
///
/// # Arguments
Expand Down Expand Up @@ -167,29 +169,94 @@ impl WalletClient {
&self,
address: &NetworkAddress,
) -> WalletResult<(Payment, PeerId)> {
match &address.as_xorname() {
Some(xorname) => {
let payment_details = self
.wallet
.get_recent_cached_payment_for_xorname(xorname)
.ok_or(WalletError::NoPaymentForAddress(*xorname))?;
let payment = payment_details.to_payment();
debug!("Payment retrieved for {xorname:?} from wallet: {payment:?}");
info!("Payment retrieved for {xorname:?} from wallet");
let peer_id = PeerId::from_bytes(&payment_details.peer_id_bytes)
.map_err(|_| WalletError::NoPaymentForAddress(*xorname))?;

Ok((payment, peer_id))
let xorname = address
.as_xorname()
.ok_or(WalletError::InvalidAddressType)?;
let mut payment_details = self
.wallet
.get_all_cached_payment_for_xorname(&xorname)
.ok_or(WalletError::NoPaymentForAddress(xorname))?;

let payment_detail = payment_details
.pop()
.ok_or(WalletError::NoPaymentForAddress(xorname))?;

let payment = payment_detail.to_payment();
debug!("Payment retrieved for {xorname:?} from wallet: {payment:?}");
info!("Payment retrieved for {xorname:?} from wallet");
let peer_id = PeerId::from_bytes(&payment_detail.peer_id_bytes)
.map_err(|_| WalletError::NoPaymentForAddress(xorname))?;

Ok((payment, peer_id))
}

/// Returns the most recent non expired cached Payment for a provided NetworkAddress.
///
/// # Arguments
/// * `address` - The [`NetworkAddress`].
///
/// # Example
/// ```no_run
/// // Getting the payment for an address using a random PeerId
/// # use sn_client::{Client, WalletClient, Error};
/// # use tempfile::TempDir;
/// # use bls::SecretKey;
/// # use sn_transfers::{HotWallet, MainSecretKey};
/// # #[tokio::main]
/// # async fn main() -> Result<(),Error>{
/// # use std::io::Bytes;
/// # let client = Client::new(SecretKey::random(), None, false, None, None).await?;
/// # let tmp_path = TempDir::new()?.path().to_owned();
/// # let mut wallet = HotWallet::load_from_path(&tmp_path,Some(MainSecretKey::new(SecretKey::random())))?;
/// use libp2p_identity::PeerId;
/// use sn_protocol::NetworkAddress;
///
/// let mut wallet_client = WalletClient::new(client, wallet);
/// let network_address = NetworkAddress::from_peer(PeerId::random());
/// let payment = wallet_client.get_non_expired_payment_for_addr(&network_address)?;
/// # Ok(())
/// # }
/// ```
pub fn get_non_expired_payment_for_addr(
&self,
address: &NetworkAddress,
) -> WalletResult<(Payment, PeerId)> {
let xorname = address
.as_xorname()
.ok_or(WalletError::InvalidAddressType)?;
let mut payment_details = self
.wallet
.get_all_cached_payment_for_xorname(&xorname)
.ok_or(WalletError::NoPaymentForAddress(xorname))?;

// find a non expired quote
let payment_detail = loop {
if let Some(payment_detail) = payment_details.pop() {
if payment_detail.quote.has_expired() {
continue;
} else {
break payment_detail;
}
} else {
return Err(WalletError::QuoteExpired(xorname));
}
None => Err(WalletError::InvalidAddressType),
}
};

let payment = payment_detail.to_payment();
debug!("Payment retrieved for {xorname:?} from wallet: {payment:?}");
info!("Payment retrieved for {xorname:?} from wallet");
let peer_id = PeerId::from_bytes(&payment_detail.peer_id_bytes)
.map_err(|_| WalletError::NoPaymentForAddress(xorname))?;

Ok((payment, peer_id))
}

/// Returns the all cached Payment for a provided NetworkAddress.
///
/// # Arguments
/// # Arguments
/// * `address` - The [`NetworkAddress`].
/// * `non_expired` - If set to true, we return all the payments that have not expired. An error is returned if
/// all the payments have expired.
///
/// # Example
/// ```no_run
Expand Down Expand Up @@ -217,35 +284,106 @@ impl WalletClient {
&self,
address: &NetworkAddress,
) -> WalletResult<Vec<(Payment, PeerId)>> {
match &address.as_xorname() {
Some(xorname) => {
let payment_details = self
.wallet
.get_all_cached_payment_for_xorname(xorname)
.ok_or(WalletError::NoPaymentForAddress(*xorname))?;
let payments = payment_details
.into_iter()
.map(|details| {
let payment = details.to_payment();
match PeerId::from_bytes(&details.peer_id_bytes) {
Ok(peer_id) => Ok((payment, peer_id)),
Err(_) => Err(WalletError::NoPaymentForAddress(*xorname)),
}
})
.collect::<WalletResult<Vec<_>>>()?;
debug!(
"{} Payment retrieved for {xorname:?} from wallet: {payments:?}",
payments.len()
);
info!(
"{} Payment retrieved for {xorname:?} from wallet",
payments.len()
);

Ok(payments)
}
None => Err(WalletError::InvalidAddressType),
let xorname = address
.as_xorname()
.ok_or(WalletError::InvalidAddressType)?;
let payment_details = self
.wallet
.get_all_cached_payment_for_xorname(&xorname)
.ok_or(WalletError::NoPaymentForAddress(xorname))?;

let payments = payment_details
.into_iter()
.map(|details| {
let payment = details.to_payment();

match PeerId::from_bytes(&details.peer_id_bytes) {
Ok(peer_id) => Ok((payment, peer_id)),
Err(_) => Err(WalletError::NoPaymentForAddress(xorname)),
}
})
.collect::<WalletResult<Vec<_>>>()?;

debug!(
"{} Payment retrieved for {xorname:?} from wallet: {payments:?}",
payments.len()
);
info!(
"{} Payment retrieved for {xorname:?} from wallet",
payments.len()
);

Ok(payments)
}

/// Returns the all cached Payment for a provided NetworkAddress that have not expired yet.
///
/// # Arguments
/// * `address` - The [`NetworkAddress`].
///
/// # Example
/// ```no_run
/// // Getting the payment for an address using a random PeerId
/// # use sn_client::{Client, WalletClient, Error};
/// # use tempfile::TempDir;
/// # use bls::SecretKey;
/// # use sn_transfers::{HotWallet, MainSecretKey};
/// # #[tokio::main]
/// # async fn main() -> Result<(),Error>{
/// # use std::io::Bytes;
/// # let client = Client::new(SecretKey::random(), None, false, None, None).await?;
/// # let tmp_path = TempDir::new()?.path().to_owned();
/// # let mut wallet = HotWallet::load_from_path(&tmp_path,Some(MainSecretKey::new(SecretKey::random())))?;
/// use libp2p_identity::PeerId;
/// use sn_protocol::NetworkAddress;
///
/// let mut wallet_client = WalletClient::new(client, wallet);
/// let network_address = NetworkAddress::from_peer(PeerId::random());
/// let payments = wallet_client.get_all_non_expired_payments_for_addr(&network_address)?;
/// # Ok(())
/// # }
/// ```
pub fn get_all_non_expired_payments_for_addr(
&self,
address: &NetworkAddress,
) -> WalletResult<Vec<(Payment, PeerId)>> {
let xorname = address
.as_xorname()
.ok_or(WalletError::InvalidAddressType)?;
let payment_details = self
.wallet
.get_all_cached_payment_for_xorname(&xorname)
.ok_or(WalletError::NoPaymentForAddress(xorname))?;

let payments = payment_details
.into_iter()
.filter_map(|details| {
if details.quote.has_expired() {
return None;
}
let payment = details.to_payment();

match PeerId::from_bytes(&details.peer_id_bytes) {
Ok(peer_id) => Some(Ok((payment, peer_id))),
Err(_) => Some(Err(WalletError::NoPaymentForAddress(xorname))),
}
})
.collect::<WalletResult<Vec<_>>>()?;

if payments.is_empty() {
return Err(WalletError::QuoteExpired(xorname));
}

debug!(
"{} Payment retrieved for {xorname:?} from wallet: {payments:?}",
payments.len()
);
info!(
"{} Payment retrieved for {xorname:?} from wallet",
payments.len()
);

Ok(payments)
}

/// Remove the payment for a given network address from disk.
Expand Down
6 changes: 3 additions & 3 deletions sn_node/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use sn_protocol::PrettyPrintRecordKey;
use sn_protocol::{NetworkAddress, PrettyPrintRecordKey};
use sn_transfers::{NanoTokens, WalletError};
use thiserror::Error;

Expand Down Expand Up @@ -55,8 +55,8 @@ pub enum Error {
InvalidQuoteContent,
#[error("The payment quote's signature is invalid")]
InvalidQuoteSignature,
#[error("The payment quote expired")]
QuoteExpired,
#[error("The payment quote expired for {0:?}")]
QuoteExpired(NetworkAddress),
/// Payment proof received has no inputs
#[error(
"Payment proof received with record:{0:?}. No payment for our node in its transaction"
Expand Down
14 changes: 3 additions & 11 deletions sn_node/src/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ use sn_networking::Network;
use sn_protocol::{error::Error as ProtocolError, NetworkAddress};
use sn_transfers::{NanoTokens, PaymentQuote};

/// The time in seconds that a quote is valid for
const QUOTE_EXPIRATION_SECS: u64 = 3600;

impl Node {
pub(crate) fn create_quote_for_storecost(
network: &Network,
Expand Down Expand Up @@ -51,14 +48,9 @@ impl Node {
return Err(Error::InvalidQuoteContent);
}

// check time
let now = std::time::SystemTime::now();
let dur_s = match now.duration_since(quote.timestamp) {
Ok(t) => t.as_secs(),
Err(_) => return Err(Error::InvalidQuoteContent),
};
if dur_s > QUOTE_EXPIRATION_SECS {
return Err(Error::QuoteExpired);
// check if the quote has expired
if quote.has_expired() {
return Err(Error::QuoteExpired(address.clone()));
}

// check sig
Expand Down
4 changes: 2 additions & 2 deletions sn_transfers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ pub use genesis::{
get_faucet_data_dir, is_genesis_parent_tx, is_genesis_spend, load_genesis_wallet,
Error as GenesisError, GENESIS_CASHNOTE, GENESIS_CASHNOTE_SK, NETWORK_ROYALTIES_PK,
};
pub use wallet::bls_secret_from_hex;
pub use wallet::{
Error as WalletError, HotWallet, Payment, PaymentQuote, Result as WalletResult, WatchOnlyWallet,
bls_secret_from_hex, Error as WalletError, HotWallet, Payment, PaymentQuote,
Result as WalletResult, WatchOnlyWallet, QUOTE_EXPIRATION_SECS,
};

// re-export crates used in our public API
Expand Down
2 changes: 1 addition & 1 deletion sn_transfers/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ use crate::{NanoTokens, UniquePubkey};
use wallet_file::wallet_file_name;

pub use self::{
data_payments::{Payment, PaymentQuote},
data_payments::{Payment, PaymentQuote, QUOTE_EXPIRATION_SECS},
error::{Error, Result},
hot_wallet::HotWallet,
keys::bls_secret_from_hex,
Expand Down
15 changes: 14 additions & 1 deletion sn_transfers/src/wallet/data_payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::{MainPubkey, NanoTokens, Transfer};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use xor_name::XorName;

use crate::{MainPubkey, NanoTokens, Transfer};
/// The time in seconds that a quote is valid for
pub const QUOTE_EXPIRATION_SECS: u64 = 3600;

#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, custom_debug::Debug)]
pub struct Payment {
Expand Down Expand Up @@ -94,6 +96,17 @@ impl PaymentQuote {
bytes
}

/// Returns true) if the quote has not yet expired
pub fn has_expired(&self) -> bool {
let now = std::time::SystemTime::now();

let dur_s = match now.duration_since(self.timestamp) {
Ok(dur) => dur.as_secs(),
Err(_) => return true,
};
dur_s > QUOTE_EXPIRATION_SECS
}

/// test utility to create a dummy quote
pub fn test_dummy(xorname: XorName, cost: NanoTokens) -> Self {
Self {
Expand Down
3 changes: 3 additions & 0 deletions sn_transfers/src/wallet/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pub enum Error {
/// No cached payment found for address
#[error("No ongoing payment found for address {0:?}")]
NoPaymentForAddress(XorName),
/// The payment Quote has expired.
#[error("The payment quote made for {0:?} has expired")]
QuoteExpired(XorName),

/// DAG error
#[error("DAG error: {0}")]
Expand Down

0 comments on commit f594eba

Please sign in to comment.