Skip to content

Commit

Permalink
Merge pull request #2511 from mickvandijke/feat-upgradable-smart-cont…
Browse files Browse the repository at this point in the history
…racts-and-updated-quotation-flow

Feat upgradable smart contracts and updated quotation flow
  • Loading branch information
grumbach authored Dec 10, 2024
2 parents 89f1357 + ffdaa02 commit 0279010
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 152 deletions.
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> {
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

0 comments on commit 0279010

Please sign in to comment.