use crate::{node::Node, Error, Result};
use libp2p::PeerId;
use sn_evm::{AttoTokens, PaymentQuote, QuotingMetrics, RewardsAddress};
use sn_networking::{calculate_cost_for_records, Network, NodeIssue};
use sn_protocol::{error::Error as ProtocolError, storage::ChunkAddress, NetworkAddress};
use std::time::Duration;
impl Node {
pub(crate) fn create_quote_for_storecost(
network: &Network,
cost: AttoTokens,
address: &NetworkAddress,
quoting_metrics: &QuotingMetrics,
bad_nodes: Vec<NetworkAddress>,
payment_address: &RewardsAddress,
) -> Result<PaymentQuote, ProtocolError> {
let content = address.as_xorname().unwrap_or_default();
let timestamp = std::time::SystemTime::now();
let serialised_bad_nodes = rmp_serde::to_vec(&bad_nodes).unwrap_or_default();
let bytes = PaymentQuote::bytes_for_signing(
content,
cost,
timestamp,
quoting_metrics,
&serialised_bad_nodes,
payment_address,
);
let Ok(signature) = network.sign(&bytes) else {
return Err(ProtocolError::QuoteGenerationFailed);
};
let quote = PaymentQuote {
content,
cost,
timestamp,
quoting_metrics: quoting_metrics.clone(),
bad_nodes: serialised_bad_nodes,
pub_key: network.get_pub_key(),
rewards_address: *payment_address,
signature,
};
debug!("Created payment quote for {address:?}: {quote:?}");
Ok(quote)
}
}
pub(crate) fn verify_quote_for_storecost(
network: &Network,
quote: PaymentQuote,
address: &NetworkAddress,
) -> Result<()> {
debug!("Verifying payment quote for {address:?}: {quote:?}");
if address.as_xorname().unwrap_or_default() != quote.content {
return Err(Error::InvalidQuoteContent);
}
if quote.has_expired() {
return Err(Error::QuoteExpired(address.clone()));
}
let bytes = quote.bytes_for_sig();
let signature = quote.signature;
if !network.verify(&bytes, &signature) {
return Err(Error::InvalidQuoteSignature);
}
Ok(())
}
pub(crate) async fn quotes_verification(network: &Network, quotes: Vec<(PeerId, PaymentQuote)>) {
if let Some((_, self_quote)) = quotes
.iter()
.find(|(peer_id, _quote)| *peer_id == network.peer_id())
{
let target_address =
NetworkAddress::from_chunk_address(ChunkAddress::new(self_quote.content));
if verify_quote_for_storecost(network, self_quote.clone(), &target_address).is_ok() {
let mut quotes_for_nodes_duty: Vec<_> = quotes
.iter()
.filter(|(peer_id, quote)| {
let is_same_target = quote.content == self_quote.content;
let is_not_self = *peer_id != network.peer_id();
let is_not_zero_quote = quote.cost != AttoTokens::zero();
let time_gap = Duration::from_secs(10);
let is_around_same_time = if quote.timestamp > self_quote.timestamp {
self_quote.timestamp + time_gap > quote.timestamp
} else {
quote.timestamp + time_gap > self_quote.timestamp
};
let is_signed_by_the_claimed_peer =
quote.check_is_signed_by_claimed_peer(*peer_id);
is_same_target
&& is_not_self
&& is_not_zero_quote
&& is_around_same_time
&& is_signed_by_the_claimed_peer
})
.cloned()
.collect();
quotes_for_nodes_duty.retain(|(peer_id, quote)| {
let cost = calculate_cost_for_records(quote.quoting_metrics.close_records_stored);
let is_same_as_expected = quote.cost == AttoTokens::from_u64(cost);
if !is_same_as_expected {
info!("Quote from {peer_id:?} using a different quoting_metrics to achieve the claimed cost. Quote {quote:?} can only result in cost {cost:?}");
network.record_node_issues(*peer_id, NodeIssue::BadQuoting);
}
is_same_as_expected
});
network.historical_verify_quotes(quotes_for_nodes_duty);
}
}
}