Skip to content

Commit

Permalink
feat: pay returns a receipt
Browse files Browse the repository at this point in the history
  • Loading branch information
mickvandijke committed Dec 9, 2024
1 parent 89f1357 commit f93e902
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 18 deletions.
33 changes: 26 additions & 7 deletions ant-evm/src/data_payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

use crate::EvmError;
use evmlib::{
common::{Address as RewardsAddress, QuoteHash}, 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};
Expand All @@ -31,31 +33,48 @@ impl EncodedPeerId {
pub fn to_peer_id(&self) -> Result<PeerId, libp2p::identity::DecodingError> {
match PublicKey::try_decode_protobuf(&self.0) {
Ok(pub_key) => Ok(PeerId::from_public_key(&pub_key)),
Err(e) => Err(e)
Err(e) => Err(e),
}
}
}

// TODO: @anselme is this conversion right?
impl From<PeerId> for EncodedPeerId {
fn from(peer_id: PeerId) -> Self {
let bytes = peer_id.to_bytes();
EncodedPeerId(bytes)
}
}

/// The proof of payment for a data payment
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct ProofOfPayment {
peer_quotes: Vec<(EncodedPeerId, PaymentQuote)>
pub peer_quotes: Vec<(EncodedPeerId, PaymentQuote)>,
}

impl ProofOfPayment {
/// 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()
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<PeerId> {
self.peer_quotes.iter().filter_map(|(peer_id, _)| peer_id.to_peer_id().ok()).collect()
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())
self.peer_quotes
.iter()
.any(|(_, quote)| quote.has_expired())
}

/// verifies the proof of payment is valid for the given peer id
Expand All @@ -72,7 +91,7 @@ impl ProofOfPayment {
Err(e) => {
warn!("Invalid encoded peer id: {e}");
return false;
},
}
};
if !quote.check_is_signed_by_claimed_peer(peer_id) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion ant-evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod amount;
mod data_payments;
mod error;

pub use data_payments::{PaymentQuote, ProofOfPayment, QUOTE_EXPIRATION_SECS};
pub use data_payments::{EncodedPeerId, PaymentQuote, ProofOfPayment, QUOTE_EXPIRATION_SECS};
pub use evmlib::quoting_metrics::QuotingMetrics;

/// Types used in the public API
Expand Down
42 changes: 39 additions & 3 deletions autonomi/src/client/payment.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,47 @@
use crate::client::data::PayError;
use crate::client::quote::StoreQuote;
use crate::Client;
use ant_evm::{AttoTokens, EvmWallet, ProofOfPayment};
use std::collections::HashMap;
use ant_evm::{AttoTokens, EncodedPeerId, EvmWallet, ProofOfPayment, QuoteHash, TxHash};
use std::collections::{BTreeMap, HashMap};
use xor_name::XorName;

/// Contains the proof of payments for each XOR address and the amount paid
pub type Receipt = HashMap<XorName, Vec<(ProofOfPayment, AttoTokens)>>;
pub type Receipt = HashMap<XorName, (ProofOfPayment, AttoTokens)>;

pub fn receipt_from_store_quotes_and_payments(
quotes: StoreQuote,
payments: BTreeMap<QuoteHash, TxHash>,
) -> Receipt {
let mut receipt = Receipt::new();

for (content_addr, quote_for_address) in quotes.0 {
let price = AttoTokens::from_atto(quote_for_address.price());

let mut proof_of_payment = ProofOfPayment {
peer_quotes: vec![],
};

for (peer_id, quote, _amount) in quote_for_address.0 {
// skip quotes that haven't been paid
if !payments.contains_key(&quote.hash()) {
continue;
}

proof_of_payment
.peer_quotes
.push((EncodedPeerId::from(peer_id), quote));
}

// skip empty proofs
if proof_of_payment.peer_quotes.is_empty() {
continue;
}

receipt.insert(content_addr, (proof_of_payment, price));
}

receipt
}

/// Payment options for data payments.
#[derive(Clone)]
Expand Down
21 changes: 17 additions & 4 deletions autonomi/src/client/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::collections::HashMap;
use xor_name::XorName;

/// A quote for a single address
pub struct QuoteForAddress(Vec<(PeerId, PaymentQuote, Amount)>);
pub struct QuoteForAddress(pub(crate) Vec<(PeerId, PaymentQuote, Amount)>);

impl QuoteForAddress {
pub fn price(&self) -> Amount {
Expand All @@ -26,7 +26,7 @@ impl QuoteForAddress {
}

/// A quote for many addresses
pub struct StoreQuote(HashMap<XorName, QuoteForAddress>);
pub struct StoreQuote(pub(crate) HashMap<XorName, QuoteForAddress>);

impl StoreQuote {
pub fn price(&self) -> Amount {
Expand Down Expand Up @@ -87,10 +87,23 @@ impl Client {
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()]));
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));
return Err(CostError::NotEnoughNodeQuotes(
content_addr,
prices.len(),
MINIMUM_QUOTES_TO_PAY,
));
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions autonomi/src/client/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use crate::client::payment::Receipt;
use crate::client::payment::{receipt_from_store_quotes_and_payments, Receipt};
use ant_evm::{EvmNetwork, EvmWallet, ProofOfPayment};
use ant_networking::{GetRecordCfg, PutRecordCfg, VerificationKind};
use ant_protocol::{
Expand Down Expand Up @@ -158,7 +158,9 @@ impl Client {
content_addrs: impl Iterator<Item = XorName>,
wallet: &EvmWallet,
) -> Result<Receipt, PayError> {
let quotes = self.get_store_quotes(wallet.network(), content_addrs).await?;
let quotes = self
.get_store_quotes(wallet.network(), content_addrs.clone())
.await?;

// Make sure nobody else can use the wallet while we are paying
debug!("Waiting for wallet lock");
Expand All @@ -168,7 +170,6 @@ 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
Expand All @@ -178,6 +179,8 @@ impl Client {
drop(lock_guard);
debug!("Unlocked wallet");

let receipt = receipt_from_store_quotes_and_payments(quotes, payments);

let skipped_chunks = content_addrs.count() - quotes.len();
trace!(
"Chunk payments of {} chunks completed. {} chunks were free / already paid for",
Expand Down

0 comments on commit f93e902

Please sign in to comment.