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

Upgradable smart contracts and updated quotation flow #2497

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c8e7b2c
feat: quoting upgrade nodeside with WIP client side
grumbach Dec 5, 2024
a958cdf
chore: revert client side attempt
grumbach Dec 5, 2024
b0c59dc
feat: add payment vault smart contract interface
mickvandijke Dec 5, 2024
7cbeb17
feat: added payment vault deploy fn
mickvandijke Dec 5, 2024
849de7e
chore: update verify data payment logic
mickvandijke Dec 5, 2024
0bf810a
feat: require 1/3 of nodes to have the data to stop quoting
grumbach Dec 6, 2024
37555c9
chore: fix compile issues in evmlib, various fixes in node and networ…
grumbach Dec 6, 2024
4ee7f93
test: add reach through proxy test
mickvandijke Dec 5, 2024
34d7004
chore: tinkering with the quote flow
mickvandijke Dec 8, 2024
38b851c
feat: wip new quoting payment integration
grumbach Dec 9, 2024
57ee232
chore: notes for takeover
grumbach Dec 9, 2024
89f1357
Merge pull request #2507 from grumbach/feat-upgradable-smart-contract…
mickvandijke Dec 9, 2024
f93e902
feat: pay returns a receipt
mickvandijke Dec 9, 2024
918d236
chore: autonomi compiles!
mickvandijke Dec 9, 2024
ffdaa02
fix: put validation verify payment import and input
mickvandijke Dec 9, 2024
0279010
Merge pull request #2511 from mickvandijke/feat-upgradable-smart-cont…
grumbach Dec 10, 2024
e8bb869
feat: compiling CLI along with various fixes
grumbach Dec 10, 2024
e8876a6
feat(node): carry out quote's payee neighbourhood check
maqi Dec 9, 2024
2556e09
chore: update payment vault interface
mickvandijke Dec 10, 2024
61e9b94
fix: include unpaid store quotes in receipt
mickvandijke Dec 10, 2024
61f0f22
fix: add rate limit to get market price RPC calls
mickvandijke Dec 10, 2024
d89cca5
Merge pull request #2517 from mickvandijke/feat-upgradable-smart-cont…
maqi Dec 10, 2024
32e1df5
Merge pull request #2513 from maqi/node_neighbourhood_verify
maqi Dec 10, 2024
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
447 changes: 263 additions & 184 deletions Cargo.lock

Large diffs are not rendered by default.

147 changes: 84 additions & 63 deletions ant-evm/src/data_payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +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::{AttoTokens, EvmError};
use evmlib::common::TxHash;
use crate::EvmError;
use evmlib::{
common::{Address as RewardsAddress, QuoteHash},
quoting_metrics::QuotingMetrics,
utils::dummy_address,
};
use libp2p::{identity::PublicKey, PeerId};
Expand All @@ -26,59 +26,89 @@ pub const QUOTE_EXPIRATION_SECS: u64 = 3600;
/// The margin allowed for live_time
const LIVE_TIME_MARGIN: u64 = 10;

#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct EncodedPeerId(Vec<u8>);

impl EncodedPeerId {
pub fn to_peer_id(&self) -> Result<PeerId, libp2p::identity::ParseError> {
PeerId::from_bytes(&self.0)
}
}

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 {
/// The Quote we're paying for
pub quote: PaymentQuote,
/// The transaction hash
pub tx_hash: TxHash,
pub peer_quotes: Vec<(EncodedPeerId, PaymentQuote)>,
}

impl ProofOfPayment {
pub fn to_peer_id_payee(&self) -> Option<PeerId> {
let pub_key = PublicKey::try_decode_protobuf(&self.quote.pub_key).ok()?;
Some(PeerId::from_public_key(&pub_key))
/// 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()
}
}

/// Quoting metrics that got used to generate a quote, or to track peer's status.
#[derive(
Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize, custom_debug::Debug,
)]
pub struct QuotingMetrics {
/// the records stored
pub close_records_stored: usize,
/// the max_records configured
pub max_records: usize,
/// number of times that got paid
pub received_payment_count: usize,
/// the duration that node keeps connected to the network, measured in hours
pub live_time: u64,
/// network density from this node's perspective, which is the responsible_range as well
/// This could be calculated via sampling, or equation calculation.
pub network_density: Option<[u8; 32]>,
/// estimated network size
pub network_size: Option<u64>,
}
/// 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()
}

impl QuotingMetrics {
/// construct an empty QuotingMetrics
pub fn new() -> Self {
Self {
close_records_stored: 0,
max_records: 0,
received_payment_count: 0,
live_time: 0,
network_density: None,
network_size: None,
}
/// has the quote expired
pub fn has_expired(&self) -> bool {
self.peer_quotes
.iter()
.any(|(_, quote)| quote.has_expired())
}
}

impl Default for QuotingMetrics {
fn default() -> Self {
Self::new()
/// 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
pub fn verify_for(&self, peer_id: PeerId) -> bool {
// make sure I am in the list of payees
if !self.payees().contains(&peer_id) {
return false;
}

// verify all signatures
for (encoded_peer_id, quote) in self.peer_quotes.iter() {
let peer_id = match encoded_peer_id.to_peer_id() {
Ok(peer_id) => peer_id,
Err(e) => {
warn!("Invalid encoded peer id: {e}");
return false;
}
};
if !quote.check_is_signed_by_claimed_peer(peer_id) {
return false;
}
}
true
}
}

Expand All @@ -89,17 +119,10 @@ impl Default for QuotingMetrics {
pub struct PaymentQuote {
/// the content paid for
pub content: XorName,
/// how much the node demands for storing the content
/// TODO: to be removed once swtich to `client querying smart_contract`
pub cost: AttoTokens,
/// the local node time when the quote was created
pub timestamp: SystemTime,
/// quoting metrics being used to generate this quote
pub quoting_metrics: QuotingMetrics,
/// list of bad_nodes that client shall not pick as a payee
/// in `serialised` format to avoid cyclic dependent on ant_protocol
#[debug(skip)]
pub bad_nodes: Vec<u8>,
/// the node's wallet address
pub rewards_address: RewardsAddress,
/// the node's libp2p identity public key in bytes (PeerId)
Expand All @@ -115,10 +138,8 @@ impl PaymentQuote {
pub fn zero() -> Self {
Self {
content: Default::default(),
cost: AttoTokens::zero(),
timestamp: SystemTime::now(),
quoting_metrics: Default::default(),
bad_nodes: vec![],
rewards_address: dummy_address(),
pub_key: vec![],
signature: vec![],
Expand All @@ -135,14 +156,11 @@ impl PaymentQuote {
/// returns the bytes to be signed from the given parameters
pub fn bytes_for_signing(
xorname: XorName,
cost: AttoTokens,
timestamp: SystemTime,
quoting_metrics: &QuotingMetrics,
serialised_bad_nodes: &[u8],
rewards_address: &RewardsAddress,
) -> Vec<u8> {
let mut bytes = xorname.to_vec();
bytes.extend_from_slice(&cost.to_bytes());
bytes.extend_from_slice(
&timestamp
.duration_since(SystemTime::UNIX_EPOCH)
Expand All @@ -152,7 +170,6 @@ impl PaymentQuote {
);
let serialised_quoting_metrics = rmp_serde::to_vec(quoting_metrics).unwrap_or_default();
bytes.extend_from_slice(&serialised_quoting_metrics);
bytes.extend_from_slice(serialised_bad_nodes);
bytes.extend_from_slice(rewards_address.as_slice());
bytes
}
Expand All @@ -161,10 +178,8 @@ impl PaymentQuote {
pub fn bytes_for_sig(&self) -> Vec<u8> {
Self::bytes_for_signing(
self.content,
self.cost,
self.timestamp,
&self.quoting_metrics,
&self.bad_nodes,
&self.rewards_address,
)
}
Expand Down Expand Up @@ -205,7 +220,7 @@ impl PaymentQuote {
true
}

/// Returns true) if the quote has not yet expired
/// Returns true if the quote has expired
pub fn has_expired(&self) -> bool {
let now = SystemTime::now();

Expand All @@ -217,13 +232,11 @@ impl PaymentQuote {
}

/// test utility to create a dummy quote
pub fn test_dummy(xorname: XorName, cost: AttoTokens) -> Self {
pub fn test_dummy(xorname: XorName) -> Self {
Self {
content: xorname,
cost,
timestamp: SystemTime::now(),
quoting_metrics: Default::default(),
bad_nodes: vec![],
pub_key: vec![],
signature: vec![],
rewards_address: dummy_address(),
Expand Down Expand Up @@ -305,6 +318,14 @@ mod tests {
use libp2p::identity::Keypair;
use std::{thread::sleep, time::Duration};

#[test]
fn test_encode_decode_peer_id() {
let id = PeerId::random();
let encoded = EncodedPeerId::from(id);
let decoded = encoded.to_peer_id().expect("decode to work");
assert_eq!(id, decoded);
}

#[test]
fn test_is_newer_than() {
let old_quote = PaymentQuote::zero();
Expand Down
4 changes: 3 additions & 1 deletion ant-evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub use evmlib::common::Address as RewardsAddress;
pub use evmlib::common::Address as EvmAddress;
pub use evmlib::common::QuotePayment;
pub use evmlib::common::{QuoteHash, TxHash};
pub use evmlib::contract::payment_vault;
pub use evmlib::cryptography;
#[cfg(feature = "external-signer")]
pub use evmlib::external_signer;
Expand All @@ -28,7 +29,8 @@ mod amount;
mod data_payments;
mod error;

pub use data_payments::{PaymentQuote, ProofOfPayment, QuotingMetrics, 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
pub use amount::{Amount, AttoTokens};
Expand Down
26 changes: 13 additions & 13 deletions ant-networking/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
log_markers::Marker,
multiaddr_pop_p2p, GetRecordCfg, GetRecordError, MsgResponder, NetworkEvent, CLOSE_GROUP_SIZE,
};
use ant_evm::{AttoTokens, PaymentQuote, QuotingMetrics};
use ant_evm::{PaymentQuote, QuotingMetrics};
use ant_protocol::{
messages::{Cmd, Request, Response},
storage::{RecordHeader, RecordKind, RecordType},
Expand Down Expand Up @@ -98,10 +98,11 @@ pub enum LocalSwarmCmd {
key: RecordKey,
sender: oneshot::Sender<Option<Record>>,
},
/// GetLocalStoreCost for this node, also with the bad_node list close to the target
GetLocalStoreCost {
/// GetLocalQuotingMetrics for this node
/// Returns the quoting metrics and whether the record at `key` is already stored locally
GetLocalQuotingMetrics {
key: RecordKey,
sender: oneshot::Sender<(AttoTokens, QuotingMetrics, Vec<NetworkAddress>)>,
sender: oneshot::Sender<(QuotingMetrics, bool)>,
},
/// Notify the node received a payment.
PaymentReceived,
Expand Down Expand Up @@ -241,8 +242,8 @@ impl Debug for LocalSwarmCmd {
"LocalSwarmCmd::GetCloseGroupLocalPeers {{ key: {key:?} }}"
)
}
LocalSwarmCmd::GetLocalStoreCost { .. } => {
write!(f, "LocalSwarmCmd::GetLocalStoreCost")
LocalSwarmCmd::GetLocalQuotingMetrics { .. } => {
write!(f, "LocalSwarmCmd::GetLocalQuotingMetrics")
}
LocalSwarmCmd::PaymentReceived => {
write!(f, "LocalSwarmCmd::PaymentReceived")
Expand Down Expand Up @@ -573,8 +574,8 @@ impl SwarmDriver {
cmd_string = "TriggerIntervalReplication";
self.try_interval_replication()?;
}
LocalSwarmCmd::GetLocalStoreCost { key, sender } => {
cmd_string = "GetLocalStoreCost";
LocalSwarmCmd::GetLocalQuotingMetrics { key, sender } => {
cmd_string = "GetLocalQuotingMetrics";
let (
_index,
_total_peers,
Expand All @@ -584,15 +585,14 @@ impl SwarmDriver {
) = self.kbuckets_status();
let estimated_network_size =
Self::estimate_network_size(peers_in_non_full_buckets, num_of_full_buckets);
let (cost, quoting_metrics) = self
let (quoting_metrics, is_already_stored) = self
.swarm
.behaviour_mut()
.kademlia
.store_mut()
.store_cost(&key, Some(estimated_network_size as u64));
.quoting_metrics(&key, Some(estimated_network_size as u64));

self.record_metrics(Marker::StoreCost {
cost: cost.as_atto(),
self.record_metrics(Marker::QuotingMetrics {
quoting_metrics: &quoting_metrics,
});

Expand Down Expand Up @@ -630,7 +630,7 @@ impl SwarmDriver {
.retain(|peer_addr| key_address.distance(peer_addr) < boundary_distance);
}

let _res = sender.send((cost, quoting_metrics, bad_nodes));
let _res = sender.send((quoting_metrics, is_already_stored));
}
LocalSwarmCmd::PaymentReceived => {
cmd_string = "PaymentReceived";
Expand Down
2 changes: 1 addition & 1 deletion ant-networking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub enum NetworkError {
OutgoingResponseDropped(Response),

#[error("Error setting up behaviour: {0}")]
BahviourErr(String),
BehaviourErr(String),

#[error("Register already exists at this address")]
RegisterAlreadyExists,
Expand Down
24 changes: 0 additions & 24 deletions ant-networking/src/event/request_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,30 +48,6 @@ impl SwarmDriver {

self.add_keys_to_replication_fetcher(holder, keys);
}
Request::Cmd(ant_protocol::messages::Cmd::QuoteVerification {
quotes,
..
}) => {
let response = Response::Cmd(
ant_protocol::messages::CmdResponse::QuoteVerification(Ok(())),
);
self.queue_network_swarm_cmd(NetworkSwarmCmd::SendResponse {
resp: response,
channel: MsgResponder::FromPeer(channel),
});

// The keypair is required to verify the quotes,
// hence throw it up to Network layer for further actions.
let quotes = quotes
.iter()
.filter_map(|(peer_address, quote)| {
peer_address
.as_peer_id()
.map(|peer_id| (peer_id, quote.clone()))
})
.collect();
self.send_event(NetworkEvent::QuoteVerification { quotes })
}
Request::Cmd(ant_protocol::messages::Cmd::PeerConsideredAsBad {
detected_by,
bad_peer,
Expand Down
Loading