diff --git a/autonomi/src/client/utils.rs b/autonomi/src/client/utils.rs index 4962b400eb..7afbdceec1 100644 --- a/autonomi/src/client/utils.rs +++ b/autonomi/src/client/utils.rs @@ -7,7 +7,6 @@ // permissions and limitations relating to use of the SAFE Network Software. use crate::client::payment::Receipt; -use crate::utils::receipt_from_cost_map_and_payments; use ant_evm::{EvmWallet, ProofOfPayment, QuotePayment}; use ant_networking::{ GetRecordCfg, Network, NetworkError, PayeeQuote, PutRecordCfg, VerificationKind, @@ -30,6 +29,7 @@ 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. diff --git a/autonomi/src/lib.rs b/autonomi/src/lib.rs index 4b3905f744..8f16bac8ae 100644 --- a/autonomi/src/lib.rs +++ b/autonomi/src/lib.rs @@ -58,3 +58,4 @@ pub use client::Client; #[cfg(feature = "extension-module")] mod python; +mod utils; diff --git a/autonomi/src/utils.rs b/autonomi/src/utils.rs new file mode 100644 index 0000000000..1348c0c685 --- /dev/null +++ b/autonomi/src/utils.rs @@ -0,0 +1,39 @@ +use crate::client::payment::Receipt; +use ant_evm::{PaymentQuote, ProofOfPayment, QuoteHash, TxHash}; +use ant_networking::PayeeQuote; +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, (_, _, v))| (k, v)).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/interface.rs b/evmlib/src/contract/payment_vault/interface.rs index bb43ac0927..d99811e01a 100644 --- a/evmlib/src/contract/payment_vault/interface.rs +++ b/evmlib/src/contract/payment_vault/interface.rs @@ -1,4 +1,6 @@ -use crate::common::{Address, Amount, QuoteHash}; +use crate::common::{Address, Amount, QuoteHash, U256}; +use crate::quoting_metrics::QuotingMetrics; +use alloy::primitives::FixedBytes; use alloy::sol; sol!( @@ -9,11 +11,25 @@ sol!( ); impl From<(QuoteHash, Address, Amount)> for IPaymentVault::DataPayment { - fn from(data: (QuoteHash, Address, Amount)) -> Self { + fn from(value: (QuoteHash, Address, Amount)) -> Self { Self { - rewardsAddress: data.1, - amount: data.2, - quoteHash: data.0, + rewardsAddress: value.1, + amount: value.2, + quoteHash: value.0, + } + } +} + +impl From for IPaymentVault::QuotingMetrics { + fn from(value: QuotingMetrics) -> Self { + Self { + closeRecordsStored: U256::from(value.close_records_stored), + maxRecords: U256::from(value.max_records), + receivedPaymentCount: U256::from(value.received_payment_count), + liveTime: U256::from(value.live_time), + networkDensity: FixedBytes::<32>::from(value.network_density.unwrap_or_default()) + .into(), + networkSize: value.network_size.map(U256::from).unwrap_or_default(), } } } diff --git a/evmlib/src/event.rs b/evmlib/src/event.rs deleted file mode 100644 index 5cdda3d91e..0000000000 --- a/evmlib/src/event.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2024 MaidSafe.net limited. -// -// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. -// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed -// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// 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, Hash, U256}; -use alloy::primitives::{b256, FixedBytes}; -use alloy::rpc::types::Log; - -// Should be updated when the smart contract changes! -pub(crate) const DATA_PAYMENT_EVENT_SIGNATURE: FixedBytes<32> = - b256!("f998960b1c6f0e0e89b7bbe6b6fbf3e03e6f08eee5b8430877d8adb8e149d580"); // DevSkim: ignore DS173237 - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Topics amount is unexpected. Was expecting 4")] - TopicsAmountUnexpected, - #[error("Event signature is missing")] - EventSignatureMissing, - #[error("Event signature does not match")] - EventSignatureDoesNotMatch, -} - -/// Struct for the ChunkPaymentEvent emitted by the ChunkPayments smart contract. -#[derive(Debug)] -pub(crate) struct ChunkPaymentEvent { - pub rewards_address: Address, - pub amount: U256, - pub quote_hash: Hash, -} - -impl TryFrom for ChunkPaymentEvent { - type Error = Error; - - fn try_from(log: Log) -> Result { - // Verify the amount of topics - if log.topics().len() != 4 { - error!("Topics amount is unexpected. Was expecting 4"); - return Err(Error::TopicsAmountUnexpected); - } - - let topic0 = log - .topics() - .first() - .ok_or(Error::EventSignatureMissing) - .inspect_err(|_| error!("Event signature is missing"))?; - - // Verify the event signature - if topic0 != &DATA_PAYMENT_EVENT_SIGNATURE { - error!( - "Event signature does not match. Expected: {:?}, got: {:?}", - DATA_PAYMENT_EVENT_SIGNATURE, topic0 - ); - return Err(Error::EventSignatureDoesNotMatch); - } - - // Extract the data - let rewards_address = Address::from_slice(&log.topics()[1][12..]); - let amount = U256::from_be_slice(&log.topics()[2][12..]); - let quote_hash = Hash::from_slice(log.topics()[3].as_slice()); - - Ok(Self { - rewards_address, - amount, - quote_hash, - }) - } -} diff --git a/evmlib/src/lib.rs b/evmlib/src/lib.rs index 331e1fbfa3..6c1054d600 100644 --- a/evmlib/src/lib.rs +++ b/evmlib/src/lib.rs @@ -6,11 +6,10 @@ // 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, QuoteHash, TxHash}; +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}; @@ -23,7 +22,6 @@ extern crate tracing; pub mod common; pub mod contract; pub mod cryptography; -pub(crate) mod event; #[cfg(feature = "external-signer")] pub mod external_signer; pub mod quoting_metrics; @@ -139,21 +137,10 @@ impl Network { pub async fn verify_data_payment( &self, - tx_hash: TxHash, quote_hash: QuoteHash, - _quoting_metrics: QuotingMetrics, + quoting_metrics: QuotingMetrics, reward_addr: Address, - quote_expiration_timestamp_in_secs: u64, - ) -> Result { - verify_data_payment( - self, - tx_hash, - quote_hash, - // quoting_metrics, // NB TODO use them here @Mick - reward_addr, - Default::default(), // NB TODO remove amounts @Mick - quote_expiration_timestamp_in_secs, - ) - .await + ) -> Result<(), transaction::Error> { + verify_data_payment(self, quote_hash, reward_addr, quoting_metrics).await } } diff --git a/evmlib/src/transaction.rs b/evmlib/src/transaction.rs index c1a86003ee..998b2051fe 100644 --- a/evmlib/src/transaction.rs +++ b/evmlib/src/transaction.rs @@ -6,216 +6,65 @@ // 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, QuoteHash, TxHash, U256}; -use crate::event::{ChunkPaymentEvent, DATA_PAYMENT_EVENT_SIGNATURE}; -use crate::Network; -use alloy::eips::BlockNumberOrTag; -use alloy::network::primitives::BlockTransactionsKind; -use alloy::primitives::FixedBytes; -use alloy::providers::{Provider, ProviderBuilder}; -use alloy::rpc::types::{Block, Filter, Log, TransactionReceipt}; +use crate::common::{Address, Amount, QuoteHash}; +use crate::contract::payment_vault::handler::PaymentVaultHandler; +use crate::quoting_metrics::QuotingMetrics; +use crate::utils::http_provider; +use crate::{contract, Network}; use alloy::transports::{RpcError, TransportErrorKind}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] RpcError(#[from] RpcError), - #[error("Transaction is not confirmed")] - TransactionUnconfirmed, - #[error("Transaction was not found")] - TransactionNotFound, - #[error("Transaction has not been included in a block yet")] - TransactionNotInBlock, - #[error("Block was not found")] - BlockNotFound, - #[error("No event proof found")] - EventProofNotFound, - #[error("Payment was done after the quote expired")] - QuoteExpired, -} - -/// Get a transaction receipt by its hash. -pub async fn get_transaction_receipt_by_hash( - network: &Network, - transaction_hash: TxHash, -) -> Result, Error> { - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .on_http(network.rpc_url().clone()); - let maybe_receipt = provider - .get_transaction_receipt(transaction_hash) - .await - .inspect_err(|err| error!("Error getting transaction receipt for transaction_hash: {transaction_hash:?} : {err:?}", ))?; - Ok(maybe_receipt) -} - -/// Get a block by its block number. -async fn get_block_by_number(network: &Network, block_number: u64) -> Result, Error> { - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .on_http(network.rpc_url().clone()); - let block = provider - .get_block_by_number( - BlockNumberOrTag::Number(block_number), - BlockTransactionsKind::Full, - ) - .await - .inspect_err(|err| error!("Error getting block by number for {block_number} : {err:?}",))?; - Ok(block) -} - -/// Get transaction logs using a filter. -async fn get_transaction_logs(network: &Network, filter: Filter) -> Result, Error> { - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .on_http(network.rpc_url().clone()); - let logs = provider - .get_logs(&filter) - .await - .inspect_err(|err| error!("Error getting logs for filter: {filter:?} : {err:?}"))?; - Ok(logs) -} - -/// Get a DataPaymentMade event, filtered by a hashed chunk address and a node address. -/// Useful for a node if it wants to check if payment for a certain chunk has been made. -async fn get_data_payment_event( - network: &Network, - block_number: u64, - quote_hash: QuoteHash, - reward_addr: Address, - amount: U256, -) -> Result, Error> { - debug!( - "Getting data payment event for quote_hash: {quote_hash:?}, reward_addr: {reward_addr:?}" - ); - let topic1: FixedBytes<32> = FixedBytes::left_padding_from(reward_addr.as_slice()); - - let filter = Filter::new() - .event_signature(DATA_PAYMENT_EVENT_SIGNATURE) - .topic1(topic1) - .topic2(amount) - .topic3(quote_hash) - .from_block(block_number) - .to_block(block_number); - - get_transaction_logs(network, filter).await + #[error(transparent)] + PaymentVaultError(#[from] contract::payment_vault::error::Error), + #[error("Payment missing")] + PaymentMissing, } /// Verify if a data payment is confirmed. pub async fn verify_data_payment( network: &Network, - tx_hash: TxHash, quote_hash: QuoteHash, reward_addr: Address, - amount: U256, - quote_expiration_timestamp_in_secs: u64, -) -> Result { - debug!("Verifying data payment for tx_hash: {tx_hash:?}"); - let transaction = get_transaction_receipt_by_hash(network, tx_hash) - .await? - .ok_or(Error::TransactionNotFound)?; - - // If the status is True, it means the tx is confirmed. - if !transaction.status() { - error!("Transaction {tx_hash:?} is not confirmed"); - return Err(Error::TransactionUnconfirmed); - } - - let block_number = transaction - .block_number - .ok_or(Error::TransactionNotInBlock) - .inspect_err(|_| error!("Transaction {tx_hash:?} has not been included in a block yet"))?; - - let block = get_block_by_number(network, block_number) - .await? - .ok_or(Error::BlockNotFound)?; - - // Check if payment was done within the quote expiration timeframe. - if quote_expiration_timestamp_in_secs < block.header.timestamp { - error!("Payment for tx_hash: {tx_hash:?} was done after the quote expired"); - return Err(Error::QuoteExpired); + quoting_metrics: QuotingMetrics, +) -> Result<(), Error> { + 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?; + + if is_paid { + Ok(()) + } else { + Err(Error::PaymentMissing) } - - let logs = - get_data_payment_event(network, block_number, quote_hash, reward_addr, amount).await?; - - for log in logs { - if log.transaction_hash != Some(tx_hash) { - // Wrong transaction. - continue; - } - - if let Ok(event) = ChunkPaymentEvent::try_from(log) { - // Check if the event matches what we expect. - if event.quote_hash == quote_hash - && event.rewards_address == reward_addr - && event.amount >= amount - { - return Ok(event.amount); - } - } - } - - error!("No event proof found for tx_hash: {tx_hash:?}"); - - Err(Error::EventProofNotFound) } #[cfg(test)] mod tests { - use crate::common::{Address, U256}; - use crate::transaction::{ - get_data_payment_event, get_transaction_receipt_by_hash, verify_data_payment, - }; + use crate::common::Address; + use crate::quoting_metrics::QuotingMetrics; + use crate::transaction::verify_data_payment; use crate::Network; use alloy::hex::FromHex; use alloy::primitives::b256; - #[tokio::test] - async fn test_get_transaction_receipt_by_hash() { - let network = Network::ArbitrumOne; - - let tx_hash = b256!("3304465f38fa0bd9670a426108dd1ddd193e059dcb7c13982d31424646217a36"); // DevSkim: ignore DS173237 - - assert!(get_transaction_receipt_by_hash(&network, tx_hash) - .await - .unwrap() - .is_some()); - } - - #[tokio::test] - async fn test_get_data_payment_event() { - let network = Network::ArbitrumOne; - - let block_number: u64 = 260246302; - let reward_address = Address::from_hex("8AB15A43305854e4AE4E6FBEa0CD1CC0AB4ecB2A").unwrap(); // DevSkim: ignore DS173237 - let amount = U256::from(1); - let quote_hash = b256!("EBD943C38C0422901D4CF22E677DD95F2591CA8D6EBFEA8BAF1BFE9FF5506ECE"); // DevSkim: ignore DS173237 - - let logs = - get_data_payment_event(&network, block_number, quote_hash, reward_address, amount) - .await - .unwrap(); - - assert_eq!(logs.len(), 1); - } - #[tokio::test] async fn test_verify_data_payment() { let network = Network::ArbitrumOne; - let tx_hash = b256!("3304465f38fa0bd9670a426108dd1ddd193e059dcb7c13982d31424646217a36"); // DevSkim: ignore DS173237 let quote_hash = b256!("EBD943C38C0422901D4CF22E677DD95F2591CA8D6EBFEA8BAF1BFE9FF5506ECE"); // DevSkim: ignore DS173237 let reward_address = Address::from_hex("8AB15A43305854e4AE4E6FBEa0CD1CC0AB4ecB2A").unwrap(); // DevSkim: ignore DS173237 - let amount = U256::from(1); let result = verify_data_payment( &network, - tx_hash, quote_hash, reward_address, - amount, - 4102441200, + QuotingMetrics::default(), ) .await;