Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat upgradable smart contracts and updated quotation flow #2511

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 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,63 @@ 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())
}

/// Returns all quotes by given peer id
pub fn quotes_by_peer(&self, peer_id: &PeerId) -> Vec<&PaymentQuote> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also check that the quote is valid for the same peer id?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is caller responsibility? The name doesn't suggest a check is there

self.peer_quotes
.iter()
.filter_map(|(id, quote)| {
if let Ok(id) = id.to_peer_id() {
if id == *peer_id {
return Some(quote);
}
}
None
})
.collect()
}

/// verifies the proof of payment is valid for the given peer id
Expand All @@ -72,7 +106,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
19 changes: 12 additions & 7 deletions ant-node/src/put_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
// permissions and limitations relating to use of the SAFE Network Software.

use crate::{node::Node, Error, Marker, Result};
use ant_evm::{AttoTokens, ProofOfPayment, QUOTE_EXPIRATION_SECS};
use ant_evm::payment_vault::verify_data_payment;
use ant_evm::{AttoTokens, ProofOfPayment};
use ant_networking::NetworkError;
use ant_protocol::storage::Transaction;
use ant_protocol::{
Expand All @@ -19,7 +20,6 @@ use ant_protocol::{
};
use ant_registers::SignedRegister;
use libp2p::kad::{Record, RecordKey};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use xor_name::XorName;

impl Node {
Expand Down Expand Up @@ -619,14 +619,19 @@ impl Node {
)));
}

let owned_payment_quotes = payment
.quotes_by_peer(&self_peer_id)
.iter()
.map(|quote| quote.hash())
.collect();

// 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(payments_to_verify)
.await
.map_err(|e| Error::EvmNetwork(format!("Failed to verify chunk payment: {e}")))?;
let reward_amount =
verify_data_payment(self.evm_network(), owned_payment_quotes, 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}");

// Notify `record_store` that the node received a payment.
Expand Down
23 changes: 14 additions & 9 deletions autonomi/src/client/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@

use bytes::Bytes;
use libp2p::kad::Quorum;
use std::collections::HashSet;

use std::collections::{HashMap, HashSet};
use std::sync::LazyLock;
use xor_name::XorName;

use crate::client::payment::PaymentOption;
use crate::client::payment::{PaymentOption, Receipt};
use crate::client::utils::process_tasks_with_max_concurrency;
use crate::client::{ClientEvent, UploadSummary};
use crate::{self_encryption::encrypt, Client};
use ant_evm::EvmWalletError;
use ant_evm::{Amount, AttoTokens};
use ant_evm::{EvmWalletError, ProofOfPayment};
use ant_networking::{GetRecordCfg, NetworkError};
use ant_protocol::{
storage::{try_deserialize_record, Chunk, ChunkAddress, RecordHeader, RecordKind},
Expand Down Expand Up @@ -84,6 +84,8 @@ pub enum PutError {
VaultBadOwner,
#[error("Payment unexpectedly invalid for {0:?}")]
PaymentUnexpectedlyInvalid(NetworkAddress),
#[error("The payment proof contains no payees.")]
PayeesMissing,
}

/// Errors that can occur during the pay operation.
Expand Down Expand Up @@ -198,7 +200,7 @@ impl Client {
if let Some(channel) = self.client_event_sender.as_ref() {
let tokens_spent = receipt
.values()
.map(|proof| proof.quote.cost.as_atto())
.map(|(_proof, price)| price.as_atto())
.sum::<Amount>();

let summary = UploadSummary {
Expand Down Expand Up @@ -261,24 +263,27 @@ impl Client {
content_addrs.len()
);

let cost_map = self
let store_quote = self
.get_store_quotes(content_addrs.into_iter())
.await
.inspect_err(|err| error!("Error getting store quotes: {err:?}"))?;

let total_cost = AttoTokens::from_atto(
cost_map
store_quote
.0
.values()
.map(|quote| quote.2.cost.as_atto())
.map(|quote| quote.price())
.sum::<Amount>(),
);

Ok(total_cost)
}

// Upload chunks and retry failed uploads up to `RETRY_ATTEMPTS` times.
pub(crate) async fn upload_chunks_with_retries<'a>(
&self,
mut chunks: Vec<&'a Chunk>,
receipt: &HashMap<XorName, ProofOfPayment>,
receipt: &Receipt,
) -> Vec<(&'a Chunk, PutError)> {
let mut current_attempt: usize = 1;

Expand All @@ -288,7 +293,7 @@ impl Client {
let self_clone = self.clone();
let address = *chunk.address();

let Some(proof) = receipt.get(chunk.name()) else {
let Some((proof, _)) = receipt.get(chunk.name()) else {
debug!("Chunk at {address:?} was already paid for so skipping");
continue;
};
Expand Down
2 changes: 1 addition & 1 deletion autonomi/src/client/data_private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl Client {
if let Some(channel) = self.client_event_sender.as_ref() {
let tokens_spent = receipt
.values()
.map(|proof| proof.quote.cost.as_atto())
.map(|(_proof, price)| price.as_atto())
.sum::<Amount>();

let summary = UploadSummary {
Expand Down
7 changes: 7 additions & 0 deletions autonomi/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mod utils;

pub use ant_evm::Amount;

use crate::EvmNetwork;
use ant_networking::{interval, multiaddr_is_global, Network, NetworkBuilder, NetworkEvent};
use ant_protocol::{version::IDENTIFY_PROTOCOL_STR, CLOSE_GROUP_SIZE};
use libp2p::{identity::Keypair, Multiaddr};
Expand Down Expand Up @@ -67,6 +68,7 @@ const CLIENT_EVENT_CHANNEL_SIZE: usize = 100;
pub struct Client {
pub(crate) network: Network,
pub(crate) client_event_sender: Arc<Option<mpsc::Sender<ClientEvent>>>,
pub(crate) evm_network: EvmNetwork,
}

/// Error returned by [`Client::connect`].
Expand Down Expand Up @@ -120,6 +122,7 @@ impl Client {
Ok(Self {
network,
client_event_sender: Arc::new(None),
evm_network: Default::default(),
})
}

Expand All @@ -130,6 +133,10 @@ impl Client {
self.client_event_sender = Arc::new(Some(client_event_sender));
client_event_receiver
}

pub fn set_evm_network(&mut self, evm_network: EvmNetwork) {
self.evm_network = evm_network;
}
}

fn build_client_and_run_swarm(local: bool) -> (Network, mpsc::Receiver<NetworkEvent>) {
Expand Down
44 changes: 40 additions & 4 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 Expand Up @@ -35,7 +71,7 @@ impl From<Receipt> for PaymentOption {
impl Client {
pub(crate) async fn pay_for_content_addrs(
&self,
content_addrs: impl Iterator<Item = XorName>,
content_addrs: impl Iterator<Item = XorName> + Clone,
payment_option: PaymentOption,
) -> Result<Receipt, PayError> {
match payment_option {
Expand Down
26 changes: 19 additions & 7 deletions autonomi/src/client/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
// permissions and limitations relating to use of the SAFE Network Software.

use super::{data::CostError, Client};
use crate::EvmNetwork;
use ant_evm::payment_vault::get_market_price;
use ant_evm::{Amount, PaymentQuote, QuotePayment};
use ant_networking::{Network, NetworkError};
Expand All @@ -17,7 +16,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 +25,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 All @@ -51,7 +50,6 @@ impl StoreQuote {
impl Client {
pub(crate) async fn get_store_quotes(
&self,
evm_network: &EvmNetwork,
content_addrs: impl Iterator<Item = XorName>,
) -> Result<StoreQuote, CostError> {
// get all quotes from nodes
Expand All @@ -68,7 +66,8 @@ impl Client {
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?;
let price =
get_market_price(&self.evm_network, quote.quoting_metrics.clone()).await?;
prices.push((peer, quote, price));
}

Expand All @@ -87,10 +86,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
Loading
Loading