From 0bf810a207b91f0a30cac52af8ba3e5497a24cbf Mon Sep 17 00:00:00 2001 From: grumbach Date: Fri, 6 Dec 2024 15:59:03 +0900 Subject: [PATCH 1/6] feat: require 1/3 of nodes to have the data to stop quoting --- ant-networking/src/lib.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ant-networking/src/lib.rs b/ant-networking/src/lib.rs index eb05e36d63..c2d196e8ae 100644 --- a/ant-networking/src/lib.rs +++ b/ant-networking/src/lib.rs @@ -392,7 +392,7 @@ impl Network { close_nodes.retain(|peer_id| !ignore_peers.contains(peer_id)); if close_nodes.is_empty() { - error!("Cann't get store_cost of {record_address:?}, as all close_nodes are ignored"); + error!("Can't get store_cost of {record_address:?}, as all close_nodes are ignored"); return Err(NetworkError::NoStoreCostResponses); } @@ -406,6 +406,10 @@ 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 + let mut peer_already_have_it = 0; + let enough_peers_already_have_it = close_nodes.len() / 3; + // loop over responses let mut all_quotes = vec![]; let mut quotes_to_pay = vec![]; @@ -438,8 +442,12 @@ impl Network { if !storage_proofs.is_empty() { debug!("Storage proofing during GetStoreQuote to be implemented."); } - info!("Address {record_address:?} was already paid for according to {peer_address:?}, ending quote request"); - return Ok(vec![]); + peer_already_have_it += 1; + 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(vec![]); + } } Err(err) => { error!("Got an error while requesting quote from peer {peer:?}: {err}"); From 37555c960547980d5f0ec821351776bc43ae22de Mon Sep 17 00:00:00 2001 From: grumbach Date: Fri, 6 Dec 2024 17:04:39 +0900 Subject: [PATCH 2/6] chore: fix compile issues in evmlib, various fixes in node and networking side --- ant-networking/src/event/request_response.rs | 24 -------------------- ant-networking/src/lib.rs | 24 +++++++------------- ant-node/src/put_validation.rs | 14 ++++++++---- ant-protocol/src/messages/cmd.rs | 19 ---------------- ant-protocol/src/messages/response.rs | 5 ---- autonomi/src/client/quote.rs | 8 +++---- autonomi/src/client/utils.rs | 12 +++++----- autonomi/src/utils.rs | 8 +++---- evmlib/src/external_signer.rs | 12 ++++++---- evmlib/src/lib.rs | 3 ++- evmlib/src/transaction.rs | 5 ++-- evmlib/tests/wallet.rs | 19 ++++++---------- 12 files changed, 51 insertions(+), 102 deletions(-) diff --git a/ant-networking/src/event/request_response.rs b/ant-networking/src/event/request_response.rs index d7a210821b..ce6755e8dc 100644 --- a/ant-networking/src/event/request_response.rs +++ b/ant-networking/src/event/request_response.rs @@ -48,30 +48,6 @@ impl SwarmDriver { self.add_keys_to_replication_fetcher(holder, keys); } - Request::Cmd(ant_protocol::messages::Cmd::QuoteVerification { - quotes, - .. - }) => { - let response = Response::Cmd( - ant_protocol::messages::CmdResponse::QuoteVerification(Ok(())), - ); - self.queue_network_swarm_cmd(NetworkSwarmCmd::SendResponse { - resp: response, - channel: MsgResponder::FromPeer(channel), - }); - - // The keypair is required to verify the quotes, - // hence throw it up to Network layer for further actions. - let quotes = quotes - .iter() - .filter_map(|(peer_address, quote)| { - peer_address - .as_peer_id() - .map(|peer_id| (peer_id, quote.clone())) - }) - .collect(); - self.send_event(NetworkEvent::QuoteVerification { quotes }) - } Request::Cmd(ant_protocol::messages::Cmd::PeerConsideredAsBad { detected_by, bad_peer, diff --git a/ant-networking/src/lib.rs b/ant-networking/src/lib.rs index c2d196e8ae..d0ee5d2c06 100644 --- a/ant-networking/src/lib.rs +++ b/ant-networking/src/lib.rs @@ -51,7 +51,7 @@ use self::{cmd::NetworkSwarmCmd, error::Result}; use ant_evm::{PaymentQuote, QuotingMetrics}; use ant_protocol::{ error::Error as ProtocolError, - messages::{ChunkProof, Cmd, Nonce, Query, QueryResponse, Request, Response}, + messages::{ChunkProof, Nonce, Query, QueryResponse, Request, Response}, storage::{RecordType, RetryStrategy, Scratchpad}, NetworkAddress, PrettyPrintKBucketKey, PrettyPrintRecordKey, CLOSE_GROUP_SIZE, }; @@ -83,8 +83,10 @@ use { std::collections::HashSet, }; -/// The type of quote for a selected payee. -pub type PayeeQuote = (PeerId, PaymentQuote); +/// 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] @@ -382,7 +384,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 @@ -446,7 +448,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(vec![]); + return Ok(SelectedQuotes { quotes: vec![] }); } } Err(err) => { @@ -458,17 +460,7 @@ impl Network { } } - // send the quotes to the other peers for verification - for peer_id in close_nodes.iter() { - let request = Request::Cmd(Cmd::QuoteVerification { - target: NetworkAddress::from_peer(*peer_id), - quotes: all_quotes.clone(), - }); - - self.send_req_ignore_reply(request, *peer_id); - } - - Ok(quotes_to_pay) + Ok(SelectedQuotes { quotes: quotes_to_pay }) } /// Get register from network. diff --git a/ant-node/src/put_validation.rs b/ant-node/src/put_validation.rs index 9a757ddf9d..0f6912e9b8 100644 --- a/ant-node/src/put_validation.rs +++ b/ant-node/src/put_validation.rs @@ -19,7 +19,7 @@ use ant_protocol::{ }; use ant_registers::SignedRegister; use libp2p::kad::{Record, RecordKey}; -use std::time::{Duration, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use xor_name::XorName; impl Node { @@ -614,7 +614,7 @@ impl Node { // 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 + let _quote_expiration_time_in_secs = quote_expiration_time .duration_since(UNIX_EPOCH) .map_err(|e| { Error::InvalidRequest(format!( @@ -622,16 +622,22 @@ impl Node { )) })? .as_secs(); + // NB TODO @mick: can we check if the quote has expired with block time in evmlib? Or should nodes do it manually here? Else keep the block below + // manually check if the quote has expired + if quote_expiration_time < SystemTime::now() { + warn!("Payment quote has expired for record {pretty_key}"); + return Err(Error::InvalidRequest(format!( + "Payment quote has expired for record {pretty_key}" + ))); + } // check if payment is valid on chain debug!("Verifying payment for record {pretty_key}"); let reward_amount = self.evm_network() .verify_data_payment( - payment.tx_hash, payment.quote.hash(), payment.quote.quoting_metrics, *self.reward_address(), - quote_expiration_time_in_secs, ) .await .map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?; diff --git a/ant-protocol/src/messages/cmd.rs b/ant-protocol/src/messages/cmd.rs index cec0629259..1437c6540b 100644 --- a/ant-protocol/src/messages/cmd.rs +++ b/ant-protocol/src/messages/cmd.rs @@ -8,7 +8,6 @@ #![allow(clippy::mutable_key_type)] // for Bytes in NetworkAddress use crate::{storage::RecordType, NetworkAddress}; -pub use ant_evm::PaymentQuote; use serde::{Deserialize, Serialize}; /// Data and CashNote cmds - recording transactions or creating, updating, and removing data. @@ -28,11 +27,6 @@ pub enum Cmd { /// Keys of copy that shall be replicated. keys: Vec<(NetworkAddress, RecordType)>, }, - /// Write operation to notify nodes a list of PaymentQuote collected. - QuoteVerification { - target: NetworkAddress, - quotes: Vec<(NetworkAddress, PaymentQuote)>, - }, /// Notify the peer it is now being considered as BAD due to the included behaviour PeerConsideredAsBad { detected_by: NetworkAddress, @@ -52,11 +46,6 @@ impl std::fmt::Debug for Cmd { .field("first_ten_keys", &first_ten_keys) .finish() } - Cmd::QuoteVerification { target, quotes } => f - .debug_struct("Cmd::QuoteVerification") - .field("target", target) - .field("quotes_len", "es.len()) - .finish(), Cmd::PeerConsideredAsBad { detected_by, bad_peer, @@ -76,7 +65,6 @@ impl Cmd { pub fn dst(&self) -> NetworkAddress { match self { Cmd::Replicate { holder, .. } => holder.clone(), - Cmd::QuoteVerification { target, .. } => target.clone(), Cmd::PeerConsideredAsBad { bad_peer, .. } => bad_peer.clone(), } } @@ -93,13 +81,6 @@ impl std::fmt::Display for Cmd { keys.len() ) } - Cmd::QuoteVerification { target, quotes } => { - write!( - f, - "Cmd::QuoteVerification(sent to {target:?} has {} quotes)", - quotes.len() - ) - } Cmd::PeerConsideredAsBad { detected_by, bad_peer, diff --git a/ant-protocol/src/messages/response.rs b/ant-protocol/src/messages/response.rs index d3fc29ab31..48b332c60b 100644 --- a/ant-protocol/src/messages/response.rs +++ b/ant-protocol/src/messages/response.rs @@ -150,11 +150,6 @@ pub enum CmdResponse { /// Response to replication cmd Replicate(Result<()>), // - // ===== QuoteVerification ===== - // - /// Response to quote verification cmd - QuoteVerification(Result<()>), - // // ===== PeerConsideredAsBad ===== // /// Response to the considered as bad notification diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs index 8b257f74d6..16cd377369 100644 --- a/autonomi/src/client/quote.rs +++ b/autonomi/src/client/quote.rs @@ -8,7 +8,7 @@ use ant_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; use ant_evm::{Amount, AttoTokens, QuotePayment}; -use ant_networking::{Network, NetworkError, PayeeQuote}; +use ant_networking::{Network, NetworkError, SelectedQuotes}; use ant_protocol::{ storage::ChunkAddress, NetworkAddress, @@ -21,7 +21,7 @@ use super::{data::CostError, Client}; pub struct QuotesToPay { pub nodes_to_pay: Vec, - pub nodes_to_upload_to: Vec, + pub nodes_to_upload_to: Vec, pub cost_per_node: AttoTokens, pub total_cost: AttoTokens, } @@ -66,7 +66,7 @@ impl Client { async fn fetch_store_quote( network: &Network, content_addr: XorName, -) -> Result, NetworkError> { +) -> Result, NetworkError> { network .get_store_quote_from_network( NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), @@ -79,7 +79,7 @@ async fn fetch_store_quote( async fn fetch_store_quote_with_retries( network: &Network, content_addr: XorName, -) -> Result<(XorName, Vec), CostError> { +) -> Result<(XorName, Vec), CostError> { let mut retries = 0; loop { diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 7afbdceec1..ba043d3158 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -9,7 +9,7 @@ use crate::client::payment::Receipt; use ant_evm::{EvmWallet, ProofOfPayment, QuotePayment}; use ant_networking::{ - GetRecordCfg, Network, NetworkError, PayeeQuote, PutRecordCfg, VerificationKind, + GetRecordCfg, Network, NetworkError, SelectedQuotes, PutRecordCfg, VerificationKind, }; use ant_protocol::{ messages::ChunkProof, @@ -197,7 +197,7 @@ impl Client { pub(crate) async fn get_store_quotes( &self, content_addrs: impl Iterator, - ) -> Result, CostError> { + ) -> Result, CostError> { let futures: Vec<_> = content_addrs .into_iter() .map(|content_addr| fetch_store_quote_with_retries(&self.network, content_addr)) @@ -205,7 +205,7 @@ impl Client { let quotes = futures::future::try_join_all(futures).await?; - Ok(quotes.into_iter().collect::>()) + Ok(quotes.into_iter().collect::>()) } } @@ -213,7 +213,7 @@ impl Client { async fn fetch_store_quote_with_retries( network: &Network, content_addr: XorName, -) -> Result<(XorName, PayeeQuote), CostError> { +) -> Result<(XorName, SelectedQuotes), CostError> { let mut retries = 0; loop { @@ -239,7 +239,7 @@ async fn fetch_store_quote_with_retries( async fn fetch_store_quote( network: &Network, content_addr: XorName, -) -> Result { +) -> Result { network .get_store_costs_from_network( NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), @@ -250,7 +250,7 @@ async fn fetch_store_quote( /// Form to be executed payments and already executed payments from a cost map. pub(crate) fn extract_quote_payments( - cost_map: &HashMap, + cost_map: &HashMap, ) -> (Vec, Vec) { let mut to_be_paid = vec![]; let mut already_paid = vec![]; diff --git a/autonomi/src/utils.rs b/autonomi/src/utils.rs index 1348c0c685..85d8f3f62c 100644 --- a/autonomi/src/utils.rs +++ b/autonomi/src/utils.rs @@ -1,17 +1,17 @@ use crate::client::payment::Receipt; use ant_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; -use ant_networking::PayeeQuote; +use ant_networking::SelectedQuotes; use std::collections::{BTreeMap, HashMap}; use xor_name::XorName; pub fn cost_map_to_quotes( - cost_map: HashMap, + cost_map: HashMap, ) -> HashMap { - cost_map.into_iter().map(|(k, (_, _, v))| (k, v)).collect() + cost_map.into_iter().map(|(k, (_, q))| (k, q)).collect() } pub fn receipt_from_cost_map_and_payments( - cost_map: HashMap, + cost_map: HashMap, payments: &BTreeMap, ) -> Receipt { let quotes = cost_map_to_quotes(cost_map); diff --git a/evmlib/src/external_signer.rs b/evmlib/src/external_signer.rs index 20c3aa95df..30186f031d 100644 --- a/evmlib/src/external_signer.rs +++ b/evmlib/src/external_signer.rs @@ -7,9 +7,8 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::common::{Address, Amount, Calldata, QuoteHash, QuotePayment, U256}; -use crate::contract::data_payments::{DataPaymentsHandler, MAX_TRANSFERS_PER_TRANSACTION}; -use crate::contract::network_token::NetworkToken; -use crate::contract::{data_payments, network_token}; +use crate::contract::network_token::{NetworkToken, self}; +use crate::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; use crate::utils::http_provider; use crate::Network; use serde::{Deserialize, Serialize}; @@ -20,7 +19,7 @@ pub enum Error { #[error("Network token contract error: {0}")] NetworkTokenContract(#[from] network_token::Error), #[error("Data payments contract error: {0}")] - DataPaymentsContract(#[from] data_payments::error::Error), + DataPaymentsContract(#[from] crate::contract::payment_vault::error::Error), } /// Approve an address / smart contract to spend this wallet's payment tokens. @@ -73,7 +72,10 @@ pub fn pay_for_quotes_calldata>( let approve_amount = total_amount; let provider = http_provider(network.rpc_url().clone()); - let data_payments = DataPaymentsHandler::new(*network.data_payments_address(), provider); + let data_payments = crate::contract::payment_vault::handler::PaymentVaultHandler::new( + *network.data_payments_address(), + provider, + ); // Divide transfers over multiple transactions if they exceed the max per transaction. let chunks = payments.chunks(MAX_TRANSFERS_PER_TRANSACTION); diff --git a/evmlib/src/lib.rs b/evmlib/src/lib.rs index 6c1054d600..a37ae2a16e 100644 --- a/evmlib/src/lib.rs +++ b/evmlib/src/lib.rs @@ -10,6 +10,7 @@ use crate::common::{Address, QuoteHash}; use crate::transaction::verify_data_payment; use alloy::primitives::address; use alloy::transports::http::reqwest; +use common::Amount; use quoting_metrics::QuotingMetrics; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; @@ -140,7 +141,7 @@ impl Network { quote_hash: QuoteHash, quoting_metrics: QuotingMetrics, reward_addr: Address, - ) -> Result<(), transaction::Error> { + ) -> Result { verify_data_payment(self, quote_hash, reward_addr, quoting_metrics).await } } diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs index 998b2051fe..cdc1262ab4 100644 --- a/evmlib/src/transaction.rs +++ b/evmlib/src/transaction.rs @@ -29,16 +29,17 @@ pub async fn verify_data_payment( quote_hash: QuoteHash, reward_addr: Address, quoting_metrics: QuotingMetrics, -) -> Result<(), Error> { +) -> 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?; + let amount_paid = Amount::ZERO; // NB TODO @mick we need to get the amount paid from the contract if is_paid { - Ok(()) + Ok(amount_paid) } else { Err(Error::PaymentMissing) } diff --git a/evmlib/tests/wallet.rs b/evmlib/tests/wallet.rs index 905f719fc3..c324f771fc 100644 --- a/evmlib/tests/wallet.rs +++ b/evmlib/tests/wallet.rs @@ -8,7 +8,8 @@ use alloy::providers::ext::AnvilApi; use alloy::providers::{ProviderBuilder, WalletProvider}; use alloy::signers::local::{LocalSigner, PrivateKeySigner}; use evmlib::common::{Amount, TxHash}; -use evmlib::contract::data_payments::MAX_TRANSFERS_PER_TRANSACTION; +use evmlib::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; +use evmlib::quoting_metrics::QuotingMetrics; use evmlib::testnet::{deploy_data_payments_contract, deploy_network_token_contract, start_node}; use evmlib::transaction::verify_data_payment; use evmlib::wallet::{transfer_tokens, wallet_address, Wallet}; @@ -67,7 +68,6 @@ async fn funded_wallet(network: &Network, genesis_wallet: EthereumWallet) -> Wal #[tokio::test] async fn test_pay_for_quotes_and_data_payment_verification() { const TRANSFERS: usize = 600; - const EXPIRATION_TIMESTAMP_IN_SECS: u64 = 4102441200; // The year 2100 let (_anvil, network, genesis_wallet) = local_testnet().await; let wallet = funded_wallet(&network, genesis_wallet).await; @@ -87,23 +87,18 @@ async fn test_pay_for_quotes_and_data_payment_verification() { unique_tx_hashes.len(), TRANSFERS.div_ceil(MAX_TRANSFERS_PER_TRANSACTION) ); - - for quote_payment in quote_payments.iter() { - let tx_hash = *tx_hashes.get("e_payment.0).unwrap(); - + for (quote_hash, reward_addr, _) in quote_payments.iter() { let result = verify_data_payment( &network, - tx_hash, - quote_payment.0, - quote_payment.1, - quote_payment.2, - EXPIRATION_TIMESTAMP_IN_SECS, + *quote_hash, + *reward_addr, + QuotingMetrics::default(), ) .await; assert!( result.is_ok(), - "Verification failed for: {quote_payment:?}. Error: {:?}", + "Verification failed for: {quote_hash:?}. Error: {:?}", result.err() ); } From 4ee7f938e3517f0c29ff1ccce4bec1dfb1aa0d29 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 5 Dec 2024 20:53:37 +0100 Subject: [PATCH 3/6] test: add reach through proxy test --- evmlib/src/contract/payment_vault/handler.rs | 6 +++--- evmlib/src/utils.rs | 2 +- evmlib/tests/payment_vault.rs | 19 ++++++++++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/evmlib/src/contract/payment_vault/handler.rs b/evmlib/src/contract/payment_vault/handler.rs index ad983e4d2b..38d1dd2da8 100644 --- a/evmlib/src/contract/payment_vault/handler.rs +++ b/evmlib/src/contract/payment_vault/handler.rs @@ -29,11 +29,11 @@ where } /// Fetch a quote from the contract - pub async fn fetch_quote( + pub async fn get_quote>( &self, - metrics: IPaymentVault::QuotingMetrics, + metrics: I, ) -> Result { - let amount = self.contract.getQuote(metrics).call().await?.price; + let amount = self.contract.getQuote(metrics.into()).call().await?.price; Ok(amount) } diff --git a/evmlib/src/utils.rs b/evmlib/src/utils.rs index 800fa7cc99..f400e4cf58 100644 --- a/evmlib/src/utils.rs +++ b/evmlib/src/utils.rs @@ -164,7 +164,7 @@ fn local_evm_network_from_csv() -> Result { } #[allow(clippy::type_complexity)] -pub(crate) fn http_provider( +pub fn http_provider( rpc_url: reqwest::Url, ) -> FillProvider< JoinFill< diff --git a/evmlib/tests/payment_vault.rs b/evmlib/tests/payment_vault.rs index b3d3ede55f..1e68e800c9 100644 --- a/evmlib/tests/payment_vault.rs +++ b/evmlib/tests/payment_vault.rs @@ -11,12 +11,15 @@ use alloy::providers::fillers::{ use alloy::providers::{Identity, ProviderBuilder, ReqwestProvider, WalletProvider}; use alloy::signers::local::{LocalSigner, PrivateKeySigner}; use alloy::transports::http::{Client, Http}; -use evmlib::common::U256; +use evmlib::common::{Amount, U256}; use evmlib::contract::network_token::NetworkToken; use evmlib::contract::payment_vault::handler::PaymentVaultHandler; use evmlib::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; +use evmlib::quoting_metrics::QuotingMetrics; use evmlib::testnet::{deploy_data_payments_contract, deploy_network_token_contract, start_node}; +use evmlib::utils::http_provider; use evmlib::wallet::wallet_address; +use evmlib::Network; async fn setup() -> ( AnvilInstance, @@ -112,6 +115,20 @@ async fn test_deploy() { setup().await; } +#[tokio::test] +async fn test_proxy_reachable() { + let network = Network::ArbitrumOne; + let provider = http_provider(network.rpc_url().clone()); + let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); + + let amount = payment_vault + .get_quote(QuotingMetrics::default()) + .await + .unwrap(); + + assert_eq!(amount, Amount::from(1)); +} + #[tokio::test] async fn test_pay_for_quotes() { let (_anvil, network_token, mut data_payments) = setup().await; From 34d7004e9f7cbc72fdb6e5d2aa5c8b102d5c1a14 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Sun, 8 Dec 2024 14:46:41 +0100 Subject: [PATCH 4/6] chore: tinkering with the quote flow --- ant-evm/src/lib.rs | 3 +- ant-node/src/put_validation.rs | 15 ++-- autonomi/src/client/data.rs | 2 + autonomi/src/client/mod.rs | 1 + autonomi/src/client/quote.rs | 60 ++++++++----- autonomi/src/client/utils.rs | 84 ++----------------- .../contract/payment_vault/implementation.rs | 1 + evmlib/src/contract/payment_vault/mod.rs | 16 ++++ evmlib/src/transaction.rs | 1 + 9 files changed, 75 insertions(+), 108 deletions(-) diff --git a/ant-evm/src/lib.rs b/ant-evm/src/lib.rs index d32ad1858f..30a42b34d4 100644 --- a/ant-evm/src/lib.rs +++ b/ant-evm/src/lib.rs @@ -13,6 +13,7 @@ pub use evmlib::common::Address as RewardsAddress; pub use evmlib::common::Address as EvmAddress; pub use evmlib::common::QuotePayment; pub use evmlib::common::{QuoteHash, TxHash}; +pub use evmlib::contract::payment_vault; pub use evmlib::cryptography; #[cfg(feature = "external-signer")] pub use evmlib::external_signer; @@ -28,8 +29,8 @@ mod amount; mod data_payments; mod error; -pub use evmlib::quoting_metrics::QuotingMetrics; pub use data_payments::{PaymentQuote, ProofOfPayment, QUOTE_EXPIRATION_SECS}; +pub use evmlib::quoting_metrics::QuotingMetrics; /// Types used in the public API pub use amount::{Amount, AttoTokens}; diff --git a/ant-node/src/put_validation.rs b/ant-node/src/put_validation.rs index 0f6912e9b8..324af47e05 100644 --- a/ant-node/src/put_validation.rs +++ b/ant-node/src/put_validation.rs @@ -622,8 +622,7 @@ impl Node { )) })? .as_secs(); - // NB TODO @mick: can we check if the quote has expired with block time in evmlib? Or should nodes do it manually here? Else keep the block below - // manually check if the quote has expired + if quote_expiration_time < SystemTime::now() { warn!("Payment quote has expired for record {pretty_key}"); return Err(Error::InvalidRequest(format!( @@ -633,7 +632,8 @@ impl Node { // check if payment is valid on chain debug!("Verifying payment for record {pretty_key}"); - let reward_amount = self.evm_network() + let reward_amount = self + .evm_network() .verify_data_payment( payment.quote.hash(), payment.quote.quoting_metrics, @@ -657,7 +657,10 @@ impl Node { .set(new_value); } self.events_channel() - .broadcast(crate::NodeEvent::RewardReceived(AttoTokens::from(reward_amount), address.clone())); + .broadcast(crate::NodeEvent::RewardReceived( + AttoTokens::from(reward_amount), + address.clone(), + )); // vdash metric (if modified please notify at https://github.com/happybeing/vdash/issues): info!("Total payment of {reward_amount:?} atto tokens accepted for record {pretty_key}"); @@ -666,7 +669,9 @@ impl Node { #[cfg(feature = "loud")] { println!("🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟 RECEIVED REWARD 🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟"); - println!("Total payment of {reward_amount:?} atto tokens accepted for record {pretty_key}"); + println!( + "Total payment of {reward_amount:?} atto tokens accepted for record {pretty_key}" + ); println!("🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟"); } diff --git a/autonomi/src/client/data.rs b/autonomi/src/client/data.rs index 113e0511a5..7070a084c7 100644 --- a/autonomi/src/client/data.rs +++ b/autonomi/src/client/data.rs @@ -123,6 +123,8 @@ pub enum CostError { CouldNotGetStoreCosts(NetworkError), #[error("Failed to serialize {0}")] Serialization(String), + #[error("Payment vault error: {0:?}")] + PaymentVaultError(ant_evm::payment_vault::error::Error), } impl Client { diff --git a/autonomi/src/client/mod.rs b/autonomi/src/client/mod.rs index f039d097a0..533fad9e70 100644 --- a/autonomi/src/client/mod.rs +++ b/autonomi/src/client/mod.rs @@ -8,6 +8,7 @@ pub mod address; pub mod payment; +pub mod quote; #[cfg(feature = "data")] pub mod archive; diff --git a/autonomi/src/client/quote.rs b/autonomi/src/client/quote.rs index 16cd377369..2f872363a2 100644 --- a/autonomi/src/client/quote.rs +++ b/autonomi/src/client/quote.rs @@ -6,18 +6,16 @@ // 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 ant_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; +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_protocol::{ - storage::ChunkAddress, - NetworkAddress, -}; -use xor_name::XorName; +use ant_protocol::{storage::ChunkAddress, NetworkAddress}; use std::collections::{BTreeMap, HashMap}; - -use crate::client::payment::Receipt; -use super::{data::CostError, Client}; +use xor_name::XorName; pub struct QuotesToPay { pub nodes_to_pay: Vec, @@ -29,6 +27,7 @@ pub struct QuotesToPay { impl Client { pub(crate) async fn get_store_quotes( &self, + network: &EvmNetwork, content_addrs: impl Iterator, ) -> Result, CostError> { let futures: Vec<_> = content_addrs @@ -39,23 +38,38 @@ impl Client { let quotes = futures::future::try_join_all(futures).await?; let mut quotes_to_pay_per_addr = HashMap::new(); - for (content_addr, quotes) in quotes { - // NB TODO: get cost from smart contract for each quote and set this value to the median of all quotes! - let cost_per_node = Amount::from(1); + + 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); + } + + // 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<_> = quotes.iter().map(|(_, q)| (q.hash(), q.rewards_address, cost_per_node)).collect(); - + 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 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), - }); + 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), + }, + ); } Ok(quotes_to_pay_per_addr) @@ -66,7 +80,7 @@ impl Client { async fn fetch_store_quote( network: &Network, content_addr: XorName, -) -> Result, NetworkError> { +) -> Result { network .get_store_quote_from_network( NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), @@ -79,7 +93,7 @@ async fn fetch_store_quote( async fn fetch_store_quote_with_retries( network: &Network, content_addr: XorName, -) -> Result<(XorName, Vec), CostError> { +) -> Result<(XorName, SelectedQuotes), CostError> { let mut retries = 0; loop { diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index ba043d3158..54e2f78e50 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -7,25 +7,22 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::client::payment::Receipt; -use ant_evm::{EvmWallet, ProofOfPayment, QuotePayment}; -use ant_networking::{ - GetRecordCfg, Network, NetworkError, SelectedQuotes, PutRecordCfg, VerificationKind, -}; +use ant_evm::{EvmWallet, ProofOfPayment}; +use ant_networking::{GetRecordCfg, PutRecordCfg, VerificationKind}; use ant_protocol::{ messages::ChunkProof, - storage::{try_serialize_record, Chunk, ChunkAddress, RecordKind, RetryStrategy}, - NetworkAddress, + storage::{try_serialize_record, Chunk, RecordKind, RetryStrategy}, }; use bytes::Bytes; use futures::stream::{FuturesUnordered, StreamExt}; use libp2p::kad::{Quorum, Record}; use rand::{thread_rng, Rng}; use self_encryption::{decrypt_full_set, DataMap, EncryptedChunk}; -use std::{collections::HashMap, future::Future, num::NonZero}; +use std::{future::Future, num::NonZero}; use xor_name::XorName; use super::{ - data::{CostError, GetError, PayError, PutError, CHUNK_DOWNLOAD_BATCH_SIZE}, + data::{GetError, PayError, PutError, CHUNK_DOWNLOAD_BATCH_SIZE}, Client, }; use crate::self_encryption::DataMapLevel; @@ -193,77 +190,6 @@ impl Client { Ok((proofs, skipped_chunks)) } - - pub(crate) async fn get_store_quotes( - &self, - content_addrs: impl Iterator, - ) -> Result, CostError> { - let futures: Vec<_> = content_addrs - .into_iter() - .map(|content_addr| fetch_store_quote_with_retries(&self.network, content_addr)) - .collect(); - - let quotes = futures::future::try_join_all(futures).await?; - - Ok(quotes.into_iter().collect::>()) - } -} - -/// Fetch a store quote for a content address with a retry strategy. -async fn fetch_store_quote_with_retries( - network: &Network, - content_addr: XorName, -) -> Result<(XorName, SelectedQuotes), CostError> { - let mut retries = 0; - - loop { - match fetch_store_quote(network, content_addr).await { - Ok(quote) => { - break Ok((content_addr, quote)); - } - Err(err) if retries < 2 => { - retries += 1; - error!("Error while fetching store quote: {err:?}, retry #{retries}"); - } - Err(err) => { - error!( - "Error while fetching store quote: {err:?}, stopping after {retries} retries" - ); - break Err(CostError::CouldNotGetStoreQuote(content_addr)); - } - } - } -} - -/// Fetch a store quote for a content address. -async fn fetch_store_quote( - network: &Network, - content_addr: XorName, -) -> Result { - network - .get_store_costs_from_network( - NetworkAddress::from_chunk_address(ChunkAddress::new(content_addr)), - vec![], - ) - .await -} - -/// Form to be executed payments and already executed payments from a cost map. -pub(crate) fn extract_quote_payments( - cost_map: &HashMap, -) -> (Vec, Vec) { - let mut to_be_paid = vec![]; - let mut already_paid = vec![]; - - for (chunk_address, (_, _, quote)) in cost_map.iter() { - if quote.cost.is_zero() { - already_paid.push(*chunk_address); - } else { - to_be_paid.push((quote.hash(), quote.rewards_address, quote.cost.as_atto())); - } - } - - (to_be_paid, already_paid) } pub(crate) async fn process_tasks_with_max_concurrency(tasks: I, batch_size: usize) -> Vec diff --git a/evmlib/src/contract/payment_vault/implementation.rs b/evmlib/src/contract/payment_vault/implementation.rs index 78ae83117c..4cbc469248 100644 --- a/evmlib/src/contract/payment_vault/implementation.rs +++ b/evmlib/src/contract/payment_vault/implementation.rs @@ -4,6 +4,7 @@ use alloy::network::{Network, ReceiptResponse, TransactionBuilder}; use alloy::providers::Provider; use alloy::transports::Transport; +// Payment Vault contract byte code const BYTE_CODE: &str = "0x60a060405230608052348015610013575f5ffd5b5061001c610021565b6100d3565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff16156100715760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100d05780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b608051610ef76100f95f395f818161064d0152818161067601526107ba0152610ef75ff3fe6080604052600436106100bf575f3560e01c8063715018a61161007c578063ad3cb1cc11610057578063ad3cb1cc14610253578063b6c2141b14610290578063cd6dc687146102af578063f2fde38b146102ce575f5ffd5b8063715018a6146101d45780638da5cb5b146101e8578063a69bf4a314610224575f5ffd5b80630716326d146100c35780633c150bf214610132578063474740b1146101605780634ec42e8e146101745780634f1ef286146101ab57806352d1902d146101c0575b5f5ffd5b3480156100ce575f5ffd5b506101086100dd366004610bc4565b600260208190525f91825260409091208054600182015491909201546001600160a01b039092169183565b604080516001600160a01b0390941684526020840192909252908201526060015b60405180910390f35b34801561013d575f5ffd5b5061015261014c366004610bf1565b50600190565b604051908152602001610129565b34801561016b575f5ffd5b506101525f5481565b34801561017f575f5ffd5b50600154610193906001600160a01b031681565b6040516001600160a01b039091168152602001610129565b6101be6101b9366004610c33565b6102ed565b005b3480156101cb575f5ffd5b5061015261030c565b3480156101df575f5ffd5b506101be610327565b3480156101f3575f5ffd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316610193565b34801561022f575f5ffd5b5061024361023e366004610cf9565b61033a565b6040519015158152602001610129565b34801561025e575f5ffd5b50610283604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516101299190610d37565b34801561029b575f5ffd5b506101be6102aa366004610d6c565b6103b6565b3480156102ba575f5ffd5b506101be6102c9366004610ddd565b6104a3565b3480156102d9575f5ffd5b506101be6102e8366004610e07565b610600565b6102f5610642565b6102fe826106e6565b61030882826106ee565b5050565b5f6103156107af565b505f516020610ea25f395f51905f5290565b61032f6107f8565b6103385f610853565b565b6040808201355f90815260026020818152838320845160608101865281546001600160a01b031681526001820154818401819052919093015494830194909452919290918401351480156103ae57506103966020840184610e07565b6001600160a01b0316815f01516001600160a01b0316145b949350505050565b5f5481908111156103da57604051630d67f41160e21b815260040160405180910390fd5b5f5b8181101561049d57368484838181106103f7576103f7610e22565b60600291909101915061042b9050336104136020840184610e07565b6001546001600160a01b0316919060208501356108c3565b6040808201355f90815260026020522081906104478282610e36565b505060408101356020820180359061045f9084610e07565b6001600160a01b03167ff998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d58060405160405180910390a4506001016103dc565b50505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156104e85750825b90505f8267ffffffffffffffff1660011480156105045750303b155b905081158015610512575080155b156105305760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561055a57845460ff60401b1916600160401b1785555b6001600160a01b03871661058157604051632d06160b60e21b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0389161790555f8690556105a93361091d565b6105b161092e565b83156105f757845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b6106086107f8565b6001600160a01b03811661063657604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61063f81610853565b50565b306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614806106c857507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166106bc5f516020610ea25f395f51905f52546001600160a01b031690565b6001600160a01b031614155b156103385760405163703e46dd60e11b815260040160405180910390fd5b61063f6107f8565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610748575060408051601f3d908101601f1916820190925261074591810190610e74565b60015b61077057604051634c9c8ce360e01b81526001600160a01b038316600482015260240161062d565b5f516020610ea25f395f51905f5281146107a057604051632a87526960e21b81526004810182905260240161062d565b6107aa8383610936565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146103385760405163703e46dd60e11b815260040160405180910390fd5b3361082a7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146103385760405163118cdaa760e01b815233600482015260240161062d565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b17905261049d90859061098b565b6109256109f7565b61063f81610a40565b6103386109f7565b61093f82610a48565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a2805115610983576107aa8282610aab565b610308610b1d565b5f5f60205f8451602086015f885af1806109aa576040513d5f823e3d81fd5b50505f513d915081156109c15780600114156109ce565b6001600160a01b0384163b155b1561049d57604051635274afe760e01b81526001600160a01b038516600482015260240161062d565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661033857604051631afcd79f60e31b815260040160405180910390fd5b6106086109f7565b806001600160a01b03163b5f03610a7d57604051634c9c8ce360e01b81526001600160a01b038216600482015260240161062d565b5f516020610ea25f395f51905f5280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f5f846001600160a01b031684604051610ac79190610e8b565b5f60405180830381855af49150503d805f8114610aff576040519150601f19603f3d011682016040523d82523d5f602084013e610b04565b606091505b5091509150610b14858383610b3c565b95945050505050565b34156103385760405163b398979f60e01b815260040160405180910390fd5b606082610b5157610b4c82610b9b565b610b94565b8151158015610b6857506001600160a01b0384163b155b15610b9157604051639996b31560e01b81526001600160a01b038516600482015260240161062d565b50805b9392505050565b805115610bab5780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f60208284031215610bd4575f5ffd5b5035919050565b5f60c08284031215610beb575f5ffd5b50919050565b5f60c08284031215610c01575f5ffd5b610b948383610bdb565b6001600160a01b038116811461063f575f5ffd5b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610c44575f5ffd5b8235610c4f81610c0b565b9150602083013567ffffffffffffffff811115610c6a575f5ffd5b8301601f81018513610c7a575f5ffd5b803567ffffffffffffffff811115610c9457610c94610c1f565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610cc357610cc3610c1f565b604052818152828201602001871015610cda575f5ffd5b816020840160208301375f602083830101528093505050509250929050565b5f5f828403610120811215610d0c575f5ffd5b610d168585610bdb565b9250606060bf1982011215610d29575f5ffd5b5060c0830190509250929050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b5f5f60208385031215610d7d575f5ffd5b823567ffffffffffffffff811115610d93575f5ffd5b8301601f81018513610da3575f5ffd5b803567ffffffffffffffff811115610db9575f5ffd5b856020606083028401011115610dcd575f5ffd5b6020919091019590945092505050565b5f5f60408385031215610dee575f5ffd5b8235610df981610c0b565b946020939093013593505050565b5f60208284031215610e17575f5ffd5b8135610b9481610c0b565b634e487b7160e01b5f52603260045260245ffd5b8135610e4181610c0b565b81546001600160a01b0319166001600160a01b039190911617815560208201356001820155604090910135600290910155565b5f60208284031215610e84575f5ffd5b5051919050565b5f82518060208501845e5f92019182525091905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca26469706673582212203894ca52be6e6323aa3d296efd566c7f21d1723d4c66c56aed8a5f75a96b579d64736f6c634300081c0033"; pub async fn deploy(provider: &P) -> Address diff --git a/evmlib/src/contract/payment_vault/mod.rs b/evmlib/src/contract/payment_vault/mod.rs index 5cbc6f7718..9581eb183e 100644 --- a/evmlib/src/contract/payment_vault/mod.rs +++ b/evmlib/src/contract/payment_vault/mod.rs @@ -1,6 +1,22 @@ +use crate::common::Amount; +use crate::contract::payment_vault::handler::PaymentVaultHandler; +use crate::quoting_metrics::QuotingMetrics; +use crate::utils::http_provider; +use crate::Network; + pub mod error; pub mod handler; pub mod implementation; 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( + network: &Network, + quoting_metrics: QuotingMetrics, +) -> Result { + let provider = http_provider(network.rpc_url().clone()); + let payment_vault = PaymentVaultHandler::new(*network.data_payments_address(), provider); + payment_vault.get_quote(quoting_metrics).await +} diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs index cdc1262ab4..11bd0c229a 100644 --- a/evmlib/src/transaction.rs +++ b/evmlib/src/transaction.rs @@ -36,6 +36,7 @@ pub async fn verify_data_payment( let 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 if is_paid { From 38b851cfc20b16af0f323c0e95f3374be4fbec20 Mon Sep 17 00:00:00 2001 From: grumbach Date: Mon, 9 Dec 2024 17:12:36 +0900 Subject: [PATCH 5/6] 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; From 57ee232a9072b869a064ce7684d0fedfb5b5eda9 Mon Sep 17 00:00:00 2001 From: grumbach Date: Mon, 9 Dec 2024 17:39:21 +0900 Subject: [PATCH 6/6] chore: notes for takeover --- autonomi/src/client/utils.rs | 6 ++---- evmlib/src/transaction.rs | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 9487f64acf..c187b5bb18 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -26,7 +26,6 @@ use super::{ Client, }; use crate::self_encryption::DataMapLevel; -use crate::utils::receipt_from_cost_map_and_payments; impl Client { /// Fetch and decrypt all chunks in the data map. @@ -169,6 +168,7 @@ impl Client { // TODO: the error might contain some succeeded quote payments as well. These should be returned on err, so that they can be skipped when retrying. // TODO: retry when it fails? // Execute chunk payments + // NB TODO: make this return a Receipt or something that can turn into a Receipt @mick let payments = wallet .pay_for_quotes(quotes.payments()) .await @@ -178,12 +178,10 @@ impl Client { drop(lock_guard); debug!("Unlocked wallet"); - 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(), + quotes.len(), skipped_chunks ); diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs index f01f2a5177..12b8f680a0 100644 --- a/evmlib/src/transaction.rs +++ b/evmlib/src/transaction.rs @@ -26,6 +26,7 @@ pub enum Error { /// Verify if a data payment is confirmed. pub async fn verify_data_payment( network: &Network, + my_quote_hashes: Vec, // TODO @mick hashes the node owns so it knows how much it received from them payment: Vec<(QuoteHash, QuotingMetrics, Address)> ) -> Result { let provider = http_provider(network.rpc_url().clone());