From 38b851cfc20b16af0f323c0e95f3374be4fbec20 Mon Sep 17 00:00:00 2001 From: grumbach Date: Mon, 9 Dec 2024 17:12:36 +0900 Subject: [PATCH] feat: wip new quoting payment integration --- ant-cli/src/commands/register.rs | 3 +- ant-evm/src/data_payments.rs | 60 +++++++-- ant-networking/src/lib.rs | 15 +-- ant-node/src/put_validation.rs | 30 ++--- autonomi/src/client/data.rs | 6 +- autonomi/src/client/payment.rs | 8 +- autonomi/src/client/quote.rs | 147 +++++++++++------------ autonomi/src/client/registers.rs | 6 +- autonomi/src/client/utils.rs | 15 ++- autonomi/src/lib.rs | 1 - autonomi/src/utils.rs | 39 ------ evmlib/src/contract/payment_vault/mod.rs | 2 +- evmlib/src/lib.rs | 6 +- evmlib/src/transaction.rs | 18 +-- evmlib/src/wallet.rs | 27 ++--- evmlib/tests/wallet.rs | 4 +- 16 files changed, 179 insertions(+), 208 deletions(-) delete mode 100644 autonomi/src/utils.rs diff --git a/ant-cli/src/commands/register.rs b/ant-cli/src/commands/register.rs index 0aad3ab844..61792766cc 100644 --- a/ant-cli/src/commands/register.rs +++ b/ant-cli/src/commands/register.rs @@ -41,9 +41,10 @@ pub async fn cost(name: &str, peers: Vec) -> Result<()> { let register_key = crate::keys::get_register_signing_key() .wrap_err("The register key is required to perform this action")?; let client = crate::actions::connect_to_network(peers).await?; + let wallet = load_wallet()?; let cost = client - .register_cost(name.to_string(), register_key) + .register_cost(&wallet.network(), name.to_string(), register_key) .await .wrap_err("Failed to get cost for register")?; info!("Estimated cost to create a register with name {name}: {cost}"); diff --git a/ant-evm/src/data_payments.rs b/ant-evm/src/data_payments.rs index f091d65290..47476893aa 100644 --- a/ant-evm/src/data_payments.rs +++ b/ant-evm/src/data_payments.rs @@ -8,7 +8,7 @@ use crate::EvmError; use evmlib::{ - common::{Address as RewardsAddress, QuoteHash, TxHash}, quoting_metrics::QuotingMetrics, utils::dummy_address + common::{Address as RewardsAddress, QuoteHash}, quoting_metrics::QuotingMetrics, utils::dummy_address }; use libp2p::{identity::PublicKey, PeerId}; use serde::{Deserialize, Serialize}; @@ -24,19 +24,61 @@ pub const QUOTE_EXPIRATION_SECS: u64 = 3600; /// The margin allowed for live_time const LIVE_TIME_MARGIN: u64 = 10; +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct EncodedPeerId(Vec); + +impl EncodedPeerId { + pub fn to_peer_id(&self) -> Result { + match PublicKey::try_decode_protobuf(&self.0) { + Ok(pub_key) => Ok(PeerId::from_public_key(&pub_key)), + Err(e) => Err(e) + } + } +} + /// The proof of payment for a data payment #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] pub struct ProofOfPayment { - /// The Quote we're paying for - pub quote: PaymentQuote, - /// The transaction hash - pub tx_hash: TxHash, + peer_quotes: Vec<(EncodedPeerId, PaymentQuote)> } impl ProofOfPayment { - pub fn to_peer_id_payee(&self) -> Option { - let pub_key = PublicKey::try_decode_protobuf(&self.quote.pub_key).ok()?; - Some(PeerId::from_public_key(&pub_key)) + /// returns a short digest of the proof of payment to use for verification + pub fn digest(&self) -> Vec<(QuoteHash, QuotingMetrics, RewardsAddress)> { + self.peer_quotes.clone().into_iter().map(|(_, quote)| (quote.hash(), quote.quoting_metrics, quote.rewards_address)).collect() + } + + /// returns the list of payees + pub fn payees(&self) -> Vec { + self.peer_quotes.iter().filter_map(|(peer_id, _)| peer_id.to_peer_id().ok()).collect() + } + + /// has the quote expired + pub fn has_expired(&self) -> bool { + self.peer_quotes.iter().any(|(_, quote)| quote.has_expired()) + } + + /// verifies the proof of payment is valid for the given peer id + pub fn verify_for(&self, peer_id: PeerId) -> bool { + // make sure I am in the list of payees + if !self.payees().contains(&peer_id) { + return false; + } + + // verify all signatures + for (encoded_peer_id, quote) in self.peer_quotes.iter() { + let peer_id = match encoded_peer_id.to_peer_id() { + Ok(peer_id) => peer_id, + Err(e) => { + warn!("Invalid encoded peer id: {e}"); + return false; + }, + }; + if !quote.check_is_signed_by_claimed_peer(peer_id) { + return false; + } + } + true } } @@ -148,7 +190,7 @@ impl PaymentQuote { true } - /// Returns true) if the quote has not yet expired + /// Returns true if the quote has expired pub fn has_expired(&self) -> bool { let now = SystemTime::now(); diff --git a/ant-networking/src/lib.rs b/ant-networking/src/lib.rs index d0ee5d2c06..7449b731da 100644 --- a/ant-networking/src/lib.rs +++ b/ant-networking/src/lib.rs @@ -83,11 +83,6 @@ use { std::collections::HashSet, }; -/// Selected quotes to pay for a data address -pub struct SelectedQuotes { - pub quotes: Vec<(PeerId, PaymentQuote)>, -} - /// Majority of a given group (i.e. > 1/2). #[inline] pub const fn close_group_majority() -> usize { @@ -384,7 +379,7 @@ impl Network { &self, record_address: NetworkAddress, ignore_peers: Vec, - ) -> Result { + ) -> Result> { // The requirement of having at least CLOSE_GROUP_SIZE // close nodes will be checked internally automatically. let mut close_nodes = self @@ -408,9 +403,9 @@ impl Network { .send_and_get_responses(&close_nodes, &request, true) .await; - // consider data to be already paid for if 1/3 of the close nodes already have it + // consider data to be already paid for if 1/2 of the close nodes already have it let mut peer_already_have_it = 0; - let enough_peers_already_have_it = close_nodes.len() / 3; + let enough_peers_already_have_it = close_nodes.len() / 2; // loop over responses let mut all_quotes = vec![]; @@ -448,7 +443,7 @@ impl Network { info!("Address {record_address:?} was already paid for according to {peer_address:?} ({peer_already_have_it}/{enough_peers_already_have_it})"); if peer_already_have_it >= enough_peers_already_have_it { info!("Address {record_address:?} was already paid for according to {peer_already_have_it} peers, ending quote request"); - return Ok(SelectedQuotes { quotes: vec![] }); + return Ok(vec![]); } } Err(err) => { @@ -460,7 +455,7 @@ impl Network { } } - Ok(SelectedQuotes { quotes: quotes_to_pay }) + Ok(quotes_to_pay) } /// Get register from network. diff --git a/ant-node/src/put_validation.rs b/ant-node/src/put_validation.rs index 324af47e05..6bfa39b496 100644 --- a/ant-node/src/put_validation.rs +++ b/ant-node/src/put_validation.rs @@ -603,27 +603,16 @@ impl Node { // check if the quote is valid let self_peer_id = self.network().peer_id(); - if !payment.quote.check_is_signed_by_claimed_peer(self_peer_id) { - warn!("Payment quote signature is not valid for record {pretty_key}"); + if !payment.verify_for(self_peer_id) { + warn!("Payment is not valid for record {pretty_key}"); return Err(Error::InvalidRequest(format!( - "Payment quote signature is not valid for record {pretty_key}" + "Payment is not valid for record {pretty_key}" ))); } - debug!("Payment quote signature is valid for record {pretty_key}"); - - // verify quote timestamp - let quote_timestamp = payment.quote.timestamp; - let quote_expiration_time = quote_timestamp + Duration::from_secs(QUOTE_EXPIRATION_SECS); - let _quote_expiration_time_in_secs = quote_expiration_time - .duration_since(UNIX_EPOCH) - .map_err(|e| { - Error::InvalidRequest(format!( - "Payment quote timestamp is invalid for record {pretty_key}: {e}" - )) - })? - .as_secs(); + debug!("Payment is valid for record {pretty_key}"); - if quote_expiration_time < SystemTime::now() { + // verify quote expiration + if payment.has_expired() { warn!("Payment quote has expired for record {pretty_key}"); return Err(Error::InvalidRequest(format!( "Payment quote has expired for record {pretty_key}" @@ -631,14 +620,11 @@ impl Node { } // check if payment is valid on chain + let payments_to_verify = payment.digest(); debug!("Verifying payment for record {pretty_key}"); let reward_amount = self .evm_network() - .verify_data_payment( - payment.quote.hash(), - payment.quote.quoting_metrics, - *self.reward_address(), - ) + .verify_data_payment(payments_to_verify) .await .map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?; debug!("Payment of {reward_amount:?} is valid for record {pretty_key}"); diff --git a/autonomi/src/client/data.rs b/autonomi/src/client/data.rs index 7070a084c7..494f3de323 100644 --- a/autonomi/src/client/data.rs +++ b/autonomi/src/client/data.rs @@ -121,10 +121,12 @@ pub enum CostError { CouldNotGetStoreQuote(XorName), #[error("Could not get store costs: {0:?}")] CouldNotGetStoreCosts(NetworkError), + #[error("Not enough node quotes for {0:?}, got: {1:?} and need at least {2:?}")] + NotEnoughNodeQuotes(XorName, usize, usize), #[error("Failed to serialize {0}")] Serialization(String), - #[error("Payment vault error: {0:?}")] - PaymentVaultError(ant_evm::payment_vault::error::Error), + #[error("Market price error: {0:?}")] + MarketPriceError(#[from] ant_evm::payment_vault::error::Error), } impl Client { diff --git a/autonomi/src/client/payment.rs b/autonomi/src/client/payment.rs index c4cdb88a03..97416e3c09 100644 --- a/autonomi/src/client/payment.rs +++ b/autonomi/src/client/payment.rs @@ -1,11 +1,11 @@ use crate::client::data::PayError; use crate::Client; -use ant_evm::{EvmWallet, ProofOfPayment}; +use ant_evm::{AttoTokens, EvmWallet, ProofOfPayment}; use std::collections::HashMap; use xor_name::XorName; -/// Contains the proof of payment for XOR addresses. -pub type Receipt = HashMap; +/// Contains the proof of payments for each XOR address and the amount paid +pub type Receipt = HashMap>; /// Payment options for data payments. #[derive(Clone)] @@ -40,7 +40,7 @@ impl Client { ) -> Result { match payment_option { PaymentOption::Wallet(wallet) => { - let (receipt, _) = self.pay(content_addrs, &wallet).await?; + let receipt = self.pay(content_addrs, &wallet).await?; Ok(receipt) } PaymentOption::Receipt(receipt) => Ok(receipt), diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs index 2f872363a2..1e5e6b80be 100644 --- a/autonomi/src/client/quote.rs +++ b/autonomi/src/client/quote.rs @@ -7,72 +7,95 @@ // permissions and limitations relating to use of the SAFE Network Software. use super::{data::CostError, Client}; -use crate::client::payment::Receipt; use crate::EvmNetwork; -use ant_evm::payment_vault::get_quote; -use ant_evm::{Amount, AttoTokens, QuotePayment}; -use ant_evm::{ProofOfPayment, QuoteHash, TxHash}; -use ant_networking::{Network, NetworkError, SelectedQuotes}; +use ant_evm::payment_vault::get_market_price; +use ant_evm::{Amount, PaymentQuote, QuotePayment}; +use ant_networking::{Network, NetworkError}; use ant_protocol::{storage::ChunkAddress, NetworkAddress}; -use std::collections::{BTreeMap, HashMap}; +use libp2p::PeerId; +use std::collections::HashMap; use xor_name::XorName; -pub struct QuotesToPay { - pub nodes_to_pay: Vec, - pub nodes_to_upload_to: Vec, - pub cost_per_node: AttoTokens, - pub total_cost: AttoTokens, +/// A quote for a single address +pub struct QuoteForAddress(Vec<(PeerId, PaymentQuote, Amount)>); + +impl QuoteForAddress { + pub fn price(&self) -> Amount { + self.0.iter().map(|(_, _, price)| price).sum() + } +} + +/// A quote for many addresses +pub struct StoreQuote(HashMap); + +impl StoreQuote { + pub fn price(&self) -> Amount { + self.0.iter().map(|(_, quote)| quote.price()).sum() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn payments(&self) -> Vec { + let mut quote_payments = vec![]; + for (_address, quote) in self.0.iter() { + for (_peer, quote, price) in quote.0.iter() { + quote_payments.push((quote.hash(), quote.rewards_address, *price)); + } + } + quote_payments + } } impl Client { pub(crate) async fn get_store_quotes( &self, - network: &EvmNetwork, + evm_network: &EvmNetwork, content_addrs: impl Iterator, - ) -> Result, CostError> { + ) -> Result { + // get all quotes from nodes let futures: Vec<_> = content_addrs .into_iter() .map(|content_addr| fetch_store_quote_with_retries(&self.network, content_addr)) .collect(); + let raw_quotes_per_addr = futures::future::try_join_all(futures).await?; - let quotes = futures::future::try_join_all(futures).await?; - + // choose the quotes to pay for each address let mut quotes_to_pay_per_addr = HashMap::new(); - - for (content_addr, selected_quotes) in quotes { - let mut prices: Vec = vec![]; - - for quote in selected_quotes.quotes { - let price = get_quote(network, quote.1.quoting_metrics.clone()).await?; - prices.push(price); + for (content_addr, raw_quotes) in raw_quotes_per_addr { + // ask smart contract for the market price + let mut prices = vec![]; + for (peer, quote) in raw_quotes { + // NB TODO @mick we need to batch this smart contract call + let price = get_market_price(evm_network, quote.quoting_metrics.clone()).await?; + prices.push((peer, quote, price)); } - // TODO: set the cost per node by picking the median price of the prices above @anselme - let cost_per_node = Amount::from(1); - - // NB TODO: that's all the nodes except the invalid ones (rejected by smart contract) - let nodes_to_pay: Vec<_> = selected_quotes - .quotes - .iter() - .map(|(_, q)| (q.hash(), q.rewards_address, cost_per_node)) - .collect(); - - // NB TODO: that's the lower half (quotes under or equal to the median price) - let nodes_to_upload_to = quotes.clone(); - - let total_cost = cost_per_node * Amount::from(nodes_to_pay.len()); - quotes_to_pay_per_addr.insert( - content_addr, - QuotesToPay { - nodes_to_pay, - nodes_to_upload_to, - cost_per_node: AttoTokens::from_atto(cost_per_node), - total_cost: AttoTokens::from_atto(total_cost), - }, - ); + // sort by price + prices.sort_by(|(_, _, price_a), (_, _, price_b)| price_a.cmp(price_b)); + + // we need at least 5 valid quotes to pay for the data + const MINIMUM_QUOTES_TO_PAY: usize = 5; + match &prices[..] { + [first, second, third, fourth, fifth, ..] => { + let (p1, q1, _) = first; + let (p2, q2, _) = second; + + // don't pay for the cheapest 2 quotes but include them + let first = (*p1, q1.clone(), Amount::ZERO); + let second = (*p2, q2.clone(), Amount::ZERO); + + // pay for the rest + quotes_to_pay_per_addr.insert(content_addr, QuoteForAddress(vec![first, second, third.clone(), fourth.clone(), fifth.clone()])); + } + _ => { + return Err(CostError::NotEnoughNodeQuotes(content_addr, prices.len(), MINIMUM_QUOTES_TO_PAY)); + } + } } - Ok(quotes_to_pay_per_addr) + Ok(StoreQuote(quotes_to_pay_per_addr)) } } @@ -80,7 +103,7 @@ impl Client { async fn fetch_store_quote( network: &Network, content_addr: XorName, -) -> Result { +) -> Result, NetworkError> { network .get_store_quote_from_network( NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), @@ -93,7 +116,7 @@ async fn fetch_store_quote( async fn fetch_store_quote_with_retries( network: &Network, content_addr: XorName, -) -> Result<(XorName, SelectedQuotes), CostError> { +) -> Result<(XorName, Vec<(PeerId, PaymentQuote)>), CostError> { let mut retries = 0; loop { @@ -114,31 +137,3 @@ async fn fetch_store_quote_with_retries( } } } - -pub fn receipt_from_quotes_and_payments( - quotes_map: HashMap, - payments: &BTreeMap, -) -> Receipt { - let quotes = cost_map_to_quotes(quotes_map); - receipt_from_quotes_and_payments("es, payments) -} - -pub fn receipt_from_quotes_and_payments( - quotes: &HashMap, - payments: &BTreeMap, -) -> Receipt { - quotes - .iter() - .filter_map(|(xor_name, quote)| { - payments.get("e.hash()).map(|tx_hash| { - ( - *xor_name, - ProofOfPayment { - quote: quote.clone(), - tx_hash: *tx_hash, - }, - ) - }) - }) - .collect() -} diff --git a/autonomi/src/client/registers.rs b/autonomi/src/client/registers.rs index c405fd6cf7..7476323ee9 100644 --- a/autonomi/src/client/registers.rs +++ b/autonomi/src/client/registers.rs @@ -11,6 +11,7 @@ use crate::client::Client; use crate::client::ClientEvent; use crate::client::UploadSummary; +use ant_evm::EvmNetwork; pub use ant_registers::{Permissions as RegisterPermissions, RegisterAddress}; pub use bls::SecretKey as RegisterSecretKey; @@ -224,6 +225,7 @@ impl Client { /// Get the cost to create a register pub async fn register_cost( &self, + evm_network: &EvmNetwork, name: String, owner: RegisterSecretKey, ) -> Result { @@ -237,7 +239,7 @@ impl Client { // get cost to store register // NB TODO: register should be priced differently from other data - let cost_map = self.get_store_quotes(std::iter::once(reg_xor)).await?; + let cost_map = self.get_store_quotes(evm_network, std::iter::once(reg_xor)).await?; let total_cost = AttoTokens::from_atto( cost_map .values() @@ -292,7 +294,7 @@ impl Client { let reg_xor = address.xorname(); debug!("Paying for register at address: {address}"); - let (payment_proofs, _skipped) = self + let payment_proofs = self .pay(std::iter::once(reg_xor), wallet) .await .inspect_err(|err| { diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 54e2f78e50..9487f64acf 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -7,7 +7,7 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::client::payment::Receipt; -use ant_evm::{EvmWallet, ProofOfPayment}; +use ant_evm::{EvmNetwork, EvmWallet, ProofOfPayment}; use ant_networking::{GetRecordCfg, PutRecordCfg, VerificationKind}; use ant_protocol::{ messages::ChunkProof, @@ -158,10 +158,8 @@ impl Client { &self, content_addrs: impl Iterator, wallet: &EvmWallet, - ) -> Result<(Receipt, Vec), PayError> { - let cost_map = self.get_store_quotes(content_addrs).await?; - - let (quote_payments, skipped_chunks) = extract_quote_payments(&cost_map); + ) -> Result { + let quotes = self.get_store_quotes(wallet.network(), content_addrs).await?; // Make sure nobody else can use the wallet while we are paying debug!("Waiting for wallet lock"); @@ -172,7 +170,7 @@ impl Client { // TODO: retry when it fails? // Execute chunk payments let payments = wallet - .pay_for_quotes(quote_payments) + .pay_for_quotes(quotes.payments()) .await .map_err(|err| PayError::from(err.0))?; @@ -182,13 +180,14 @@ impl Client { let proofs = receipt_from_cost_map_and_payments(cost_map, &payments); + let skipped_chunks = content_addrs.count() - quotes.len(); trace!( "Chunk payments of {} chunks completed. {} chunks were free / already paid for", proofs.len(), - skipped_chunks.len() + skipped_chunks ); - Ok((proofs, skipped_chunks)) + Ok(receipt) } } diff --git a/autonomi/src/lib.rs b/autonomi/src/lib.rs index 8f16bac8ae..4b3905f744 100644 --- a/autonomi/src/lib.rs +++ b/autonomi/src/lib.rs @@ -58,4 +58,3 @@ pub use client::Client; #[cfg(feature = "extension-module")] mod python; -mod utils; diff --git a/autonomi/src/utils.rs b/autonomi/src/utils.rs deleted file mode 100644 index 85d8f3f62c..0000000000 --- a/autonomi/src/utils.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::client::payment::Receipt; -use ant_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; -use ant_networking::SelectedQuotes; -use std::collections::{BTreeMap, HashMap}; -use xor_name::XorName; - -pub fn cost_map_to_quotes( - cost_map: HashMap, -) -> HashMap { - cost_map.into_iter().map(|(k, (_, q))| (k, q)).collect() -} - -pub fn receipt_from_cost_map_and_payments( - cost_map: HashMap, - payments: &BTreeMap, -) -> Receipt { - let quotes = cost_map_to_quotes(cost_map); - receipt_from_quotes_and_payments("es, payments) -} - -pub fn receipt_from_quotes_and_payments( - quotes: &HashMap, - payments: &BTreeMap, -) -> Receipt { - quotes - .iter() - .filter_map(|(xor_name, quote)| { - payments.get("e.hash()).map(|tx_hash| { - ( - *xor_name, - ProofOfPayment { - quote: quote.clone(), - tx_hash: *tx_hash, - }, - ) - }) - }) - .collect() -} diff --git a/evmlib/src/contract/payment_vault/mod.rs b/evmlib/src/contract/payment_vault/mod.rs index 9581eb183e..d6afbbd91a 100644 --- a/evmlib/src/contract/payment_vault/mod.rs +++ b/evmlib/src/contract/payment_vault/mod.rs @@ -12,7 +12,7 @@ pub mod interface; pub const MAX_TRANSFERS_PER_TRANSACTION: usize = 256; /// Helper function to return a quote for the given quoting metrics -pub async fn get_quote( +pub async fn get_market_price( network: &Network, quoting_metrics: QuotingMetrics, ) -> Result { diff --git a/evmlib/src/lib.rs b/evmlib/src/lib.rs index a37ae2a16e..1bc363925a 100644 --- a/evmlib/src/lib.rs +++ b/evmlib/src/lib.rs @@ -138,10 +138,8 @@ impl Network { pub async fn verify_data_payment( &self, - quote_hash: QuoteHash, - quoting_metrics: QuotingMetrics, - reward_addr: Address, + payment: Vec<(QuoteHash, QuotingMetrics, Address)> ) -> Result { - verify_data_payment(self, quote_hash, reward_addr, quoting_metrics).await + verify_data_payment(self, payment).await } } diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs index 11bd0c229a..f01f2a5177 100644 --- a/evmlib/src/transaction.rs +++ b/evmlib/src/transaction.rs @@ -26,16 +26,18 @@ pub enum Error { /// Verify if a data payment is confirmed. pub async fn verify_data_payment( network: &Network, - quote_hash: QuoteHash, - reward_addr: Address, - quoting_metrics: QuotingMetrics, + payment: Vec<(QuoteHash, QuotingMetrics, Address)> ) -> Result { let provider = http_provider(network.rpc_url().clone()); let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); - let is_paid = payment_vault - .verify_payment(quoting_metrics, (quote_hash, reward_addr, Amount::ZERO)) - .await?; + // NB TODO @mick remove tmp loop and support verification of the whole payment at once + let mut is_paid = true; + for (quote_hash, quoting_metrics, reward_addr) in payment { + is_paid = payment_vault + .verify_payment(quoting_metrics, (quote_hash, reward_addr, Amount::ZERO)) + .await?; + } let amount_paid = Amount::ZERO; // NB TODO @mick we need to get the amount paid from the contract @@ -64,9 +66,7 @@ mod tests { let result = verify_data_payment( &network, - quote_hash, - reward_address, - QuotingMetrics::default(), + vec![(quote_hash, QuotingMetrics::default(), reward_address)] ) .await; diff --git a/evmlib/src/wallet.rs b/evmlib/src/wallet.rs index 53cfe2673f..327c0faf40 100644 --- a/evmlib/src/wallet.rs +++ b/evmlib/src/wallet.rs @@ -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 crate::common::{Address, Amount, QuoteHash, QuotePayment, TxHash, U256}; +use crate::common::{Address, Amount, QuotePayment, QuoteHash, TxHash, U256}; use crate::contract::network_token::NetworkToken; use crate::contract::payment_vault::handler::PaymentVaultHandler; use crate::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; @@ -120,26 +120,13 @@ impl Wallet { approve_to_spend_tokens(self.wallet.clone(), &self.network, spender, amount).await } - /// Pays for a single quote. Returns transaction hash of the payment. - pub async fn pay_for_quote( - &self, - quote_hash: QuoteHash, - rewards_addr: Address, - amount: U256, - ) -> Result { - self.pay_for_quotes([(quote_hash, rewards_addr, amount)]) - .await - .map(|v| v.values().last().cloned().expect("Infallible")) - .map_err(|err| err.0) - } - /// Function for batch payments of quotes. It accepts an iterator of QuotePayment and returns /// transaction hashes of the payments by quotes. pub async fn pay_for_quotes>( &self, - data_payments: I, + quote_payments: I, ) -> Result, PayForQuotesError> { - pay_for_quotes(self.wallet.clone(), &self.network, data_payments).await + pay_for_quotes(self.wallet.clone(), &self.network, quote_payments).await } /// Build a provider using this wallet. @@ -334,8 +321,14 @@ pub async fn pay_for_quotes>( let provider = http_provider_with_wallet(network.rpc_url().clone(), wallet); let data_payments = PaymentVaultHandler::new(*network.data_payments_address(), provider); + // remove payments with 0 amount as they don't need to be paid for + let payment_for_batch: Vec = payments + .into_iter() + .filter(|(_, _, amount)| *amount > Amount::ZERO) + .collect(); + // Divide transfers over multiple transactions if they exceed the max per transaction. - let chunks = payments.chunks(MAX_TRANSFERS_PER_TRANSACTION); + let chunks = payment_for_batch.chunks(MAX_TRANSFERS_PER_TRANSACTION); let mut tx_hashes_by_quote = BTreeMap::new(); diff --git a/evmlib/tests/wallet.rs b/evmlib/tests/wallet.rs index c324f771fc..cab48d254b 100644 --- a/evmlib/tests/wallet.rs +++ b/evmlib/tests/wallet.rs @@ -90,9 +90,7 @@ async fn test_pay_for_quotes_and_data_payment_verification() { for (quote_hash, reward_addr, _) in quote_payments.iter() { let result = verify_data_payment( &network, - *quote_hash, - *reward_addr, - QuotingMetrics::default(), + vec![(*quote_hash, QuotingMetrics::default(), *reward_addr)] ) .await;