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 rebased #2519

Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
69da965
feat: quoting upgrade nodeside with WIP client side
grumbach Dec 5, 2024
fdf2c89
chore: revert client side attempt
grumbach Dec 5, 2024
f096059
feat: add payment vault smart contract interface
mickvandijke Dec 5, 2024
7a14f04
feat: added payment vault deploy fn
mickvandijke Dec 5, 2024
74d03a1
chore: update verify data payment logic
mickvandijke Dec 5, 2024
5274730
feat: require 1/3 of nodes to have the data to stop quoting
grumbach Dec 6, 2024
71de86b
chore: fix compile issues in evmlib, various fixes in node and networ…
grumbach Dec 6, 2024
558bdc0
test: add reach through proxy test
mickvandijke Dec 5, 2024
0beceba
chore: tinkering with the quote flow
mickvandijke Dec 8, 2024
b306802
feat: wip new quoting payment integration
grumbach Dec 9, 2024
ef02c02
chore: notes for takeover
grumbach Dec 9, 2024
c950dec
feat: pay returns a receipt
mickvandijke Dec 9, 2024
222daf4
chore: autonomi compiles!
mickvandijke Dec 9, 2024
f692b2e
fix: put validation verify payment import and input
mickvandijke Dec 9, 2024
d3cdc3e
feat: compiling CLI along with various fixes
grumbach Dec 10, 2024
d1fa2a2
chore: update payment vault interface
mickvandijke Dec 10, 2024
e3bef52
fix: include unpaid store quotes in receipt
mickvandijke Dec 10, 2024
83644f8
fix: add rate limit to get market price RPC calls
mickvandijke Dec 10, 2024
7bfce83
feat(node): carry out quote's payee neighbourhood check
maqi Dec 9, 2024
9bb49c7
chore: compile and fixes after rebase
grumbach Dec 11, 2024
7766ff0
chore: clippy
grumbach Dec 11, 2024
9592fa4
ci: increase upload timeout
grumbach Dec 11, 2024
d4d6e8c
fix: prepare for smart contract quote batching
grumbach Dec 11, 2024
06d93e7
chore: remove excessive debug log
mickvandijke Dec 11, 2024
e85d4e4
chore: update payment vault interface and implementation
mickvandijke Dec 11, 2024
ef9822d
chore: introduce rate limiter and retry strategy for `get_market_price`
mickvandijke Dec 11, 2024
abf651c
Merge pull request #3 from mickvandijke/feat-upgradable-smart-contrac…
mickvandijke Dec 11, 2024
bc792f7
Merge remote-tracking branch 'refs/remotes/maidsafe/main' into feat-u…
mickvandijke Dec 11, 2024
72fa1a9
chore: delete print
mickvandijke Dec 11, 2024
80191e8
Merge pull request #5 from mickvandijke/feat-upgradable-smart-contrac…
mickvandijke Dec 11, 2024
d2df2cc
fix: assume that content_addrs with no quotes are already uploaded
mickvandijke Dec 11, 2024
7ce3236
chore: set arbitrum sepolia data payments address
mickvandijke Dec 11, 2024
14d489e
Merge remote-tracking branch 'maidsafe/main' into feat-upgradable-sma…
mickvandijke Dec 11, 2024
b173d28
Merge pull request #6 from mickvandijke/feat-upgradable-smart-contrac…
mickvandijke Dec 11, 2024
b6b230f
fix: clippy error
mickvandijke Dec 11, 2024
0089864
chore: update contract interface
mickvandijke Dec 12, 2024
fd2059d
fix: update get_quotes_by_peer
mickvandijke Dec 12, 2024
86fcaac
fix: clippy error
mickvandijke Dec 12, 2024
01214a9
Merge remote-tracking branch 'upstream/main' into feat-upgradable-sma…
jacderida Dec 12, 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
418 changes: 241 additions & 177 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
Loading