Skip to content

Commit

Permalink
Merge branch 'main' into gustavo/fix-release-ci
Browse files Browse the repository at this point in the history
  • Loading branch information
gusinacio authored Jan 29, 2025
2 parents b89a379 + b8e424e commit e433a8a
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 5 deletions.
5 changes: 5 additions & 0 deletions tap_graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ tap_receipt = { version = "0.1.0", path = "../tap_receipt" }

[dev-dependencies]
rstest.workspace = true


[features]
default = []
v2 = []
9 changes: 5 additions & 4 deletions tap_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
//! These structs are used for communication between The Graph systems.
//!
mod rav;
mod receipt;
mod v1;

pub use rav::{ReceiptAggregateVoucher, SignedRav};
pub use receipt::{Receipt, SignedReceipt};
#[cfg(any(test, feature = "v2"))]
pub mod v2;

pub use v1::{Receipt, ReceiptAggregateVoucher, SignedRav, SignedReceipt};
8 changes: 8 additions & 0 deletions tap_graph/src/v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2023-, Semiotic AI, Inc.
// SPDX-License-Identifier: Apache-2.0

mod rav;
mod receipt;

pub use rav::{ReceiptAggregateVoucher, SignedRav};
pub use receipt::{Receipt, SignedReceipt};
2 changes: 1 addition & 1 deletion tap_graph/src/rav.rs → tap_graph/src/v1/rav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use tap_receipt::{
ReceiptWithState, WithValueAndTimestamp,
};

use crate::{receipt::Receipt, SignedReceipt};
use super::{Receipt, SignedReceipt};

/// A Rav wrapped in an Eip712SignedMessage
pub type SignedRav = Eip712SignedMessage<ReceiptAggregateVoucher>;
Expand Down
File renamed without changes.
8 changes: 8 additions & 0 deletions tap_graph/src/v2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2023-, Semiotic AI, Inc.
// SPDX-License-Identifier: Apache-2.0

mod rav;
mod receipt;

pub use rav::{ReceiptAggregateVoucher, SignedRav};
pub use receipt::{Receipt, SignedReceipt};
131 changes: 131 additions & 0 deletions tap_graph/src/v2/rav.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2023-, Semiotic AI, Inc.
// SPDX-License-Identifier: Apache-2.0

//! # Receipt Aggregate Voucher v2
use std::cmp;

use alloy::{
primitives::{Address, Bytes},
sol,
};
use serde::{Deserialize, Serialize};
use tap_eip712_message::Eip712SignedMessage;
use tap_receipt::{
rav::{Aggregate, AggregationError},
state::Checked,
ReceiptWithState, WithValueAndTimestamp,
};

use super::{Receipt, SignedReceipt};

/// EIP712 signed message for ReceiptAggregateVoucher
pub type SignedRav = Eip712SignedMessage<ReceiptAggregateVoucher>;

sol! {
/// Holds information needed for promise of payment signed with ECDSA
///
/// We use camelCase for field names to match the Ethereum ABI encoding
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
struct ReceiptAggregateVoucher {
/// Unique allocation id this RAV belongs to
address allocationId;
// The address of the payer the RAV was issued by
address payer;
// The address of the data service the RAV was issued to
address dataService;
// The address of the service provider the RAV was issued to
address serviceProvider;
// The RAV timestamp, indicating the latest TAP Receipt in the RAV
uint64 timestampNs;
// Total amount owed to the service provider since the beginning of the
// payer-service provider relationship, including all debt that is already paid for.
uint128 valueAggregate;
// Arbitrary metadata to extend functionality if a data service requires it
bytes metadata;
}
}

impl ReceiptAggregateVoucher {
/// Aggregates a batch of validated receipts with optional validated previous RAV,
/// returning a new RAV if all provided items are valid or an error if not.
///
/// # Errors
///
/// Returns [`Error::AggregateOverflow`] if any receipt value causes aggregate
/// value to overflow
pub fn aggregate_receipts(
allocation_id: Address,
payer: Address,
data_service: Address,
service_provider: Address,
receipts: &[Eip712SignedMessage<Receipt>],
previous_rav: Option<Eip712SignedMessage<Self>>,
) -> Result<Self, AggregationError> {
//TODO(#29): When receipts in flight struct in created check that the state
// of every receipt is OK with all checks complete (relies on #28)
// If there is a previous RAV get initialize values from it, otherwise get default values
let mut timestamp_max = 0u64;
let mut value_aggregate = 0u128;

if let Some(prev_rav) = previous_rav {
timestamp_max = prev_rav.message.timestampNs;
value_aggregate = prev_rav.message.valueAggregate;
}

for receipt in receipts {
value_aggregate = value_aggregate
.checked_add(receipt.message.value)
.ok_or(AggregationError::AggregateOverflow)?;

timestamp_max = cmp::max(timestamp_max, receipt.message.timestamp_ns)
}

Ok(Self {
allocationId: allocation_id,
timestampNs: timestamp_max,
valueAggregate: value_aggregate,
payer,
dataService: data_service,
serviceProvider: service_provider,
metadata: Bytes::new(),
})
}
}

impl Aggregate<SignedReceipt> for ReceiptAggregateVoucher {
fn aggregate_receipts(
receipts: &[ReceiptWithState<Checked, SignedReceipt>],
previous_rav: Option<Eip712SignedMessage<Self>>,
) -> Result<Self, AggregationError> {
if receipts.is_empty() {
return Err(AggregationError::NoValidReceiptsForRavRequest);
}
let allocation_id = receipts[0].signed_receipt().message.allocation_id;
let payer = receipts[0].signed_receipt().message.payer;
let data_service = receipts[0].signed_receipt().message.data_service;
let service_provider = receipts[0].signed_receipt().message.service_provider;
let receipts = receipts
.iter()
.map(|rx_receipt| rx_receipt.signed_receipt().clone())
.collect::<Vec<_>>();
ReceiptAggregateVoucher::aggregate_receipts(
allocation_id,
payer,
data_service,
service_provider,
receipts.as_slice(),
previous_rav,
)
}
}

impl WithValueAndTimestamp for ReceiptAggregateVoucher {
fn value(&self) -> u128 {
self.valueAggregate
}

fn timestamp_ns(&self) -> u64 {
self.timestampNs
}
}
158 changes: 158 additions & 0 deletions tap_graph/src/v2/receipt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2023-, Semiotic AI, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Receipt v2
use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};

use alloy::{primitives::Address, sol};
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use tap_eip712_message::Eip712SignedMessage;
use tap_receipt::WithValueAndTimestamp;

/// A signed receipt message
pub type SignedReceipt = Eip712SignedMessage<Receipt>;

sol! {
/// Holds information needed for promise of payment signed with ECDSA
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
struct Receipt {
/// Unique allocation id this receipt belongs to
address allocation_id;

// The address of the payer the RAV was issued by
address payer;
// The address of the data service the RAV was issued to
address data_service;
// The address of the service provider the RAV was issued to
address service_provider;

/// Unix Epoch timestamp in nanoseconds (Truncated to 64-bits)
uint64 timestamp_ns;
/// Random value used to avoid collisions from multiple receipts with one timestamp
uint64 nonce;
/// GRT value for transaction (truncate to lower bits)
uint128 value;
}
}

fn get_current_timestamp_u64_ns() -> Result<u64, SystemTimeError> {
Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos() as u64)
}
impl Receipt {
/// Returns a receipt with provided values
pub fn new(
allocation_id: Address,
payer: Address,
data_service: Address,
service_provider: Address,
value: u128,
) -> Result<Self, SystemTimeError> {
let timestamp_ns = get_current_timestamp_u64_ns()?;
let nonce = thread_rng().gen::<u64>();
Ok(Self {
allocation_id,
payer,
data_service,
service_provider,
timestamp_ns,
nonce,
value,
})
}
}

impl WithValueAndTimestamp for Receipt {
fn value(&self) -> u128 {
self.value
}

fn timestamp_ns(&self) -> u64 {
self.timestamp_ns
}
}

#[cfg(test)]
mod receipt_unit_test {
use std::time::{SystemTime, UNIX_EPOCH};

use alloy::primitives::address;
use rstest::*;

use super::*;

#[fixture]
fn allocation_id() -> Address {
address!("1234567890abcdef1234567890abcdef12345678")
}

#[fixture]
fn payer() -> Address {
address!("abababababababababababababababababababab")
}

#[fixture]
fn data_service() -> Address {
address!("deaddeaddeaddeaddeaddeaddeaddeaddeaddead")
}

#[fixture]
fn service_provider() -> Address {
address!("beefbeefbeefbeefbeefbeefbeefbeefbeefbeef")
}

#[fixture]
fn value() -> u128 {
1234
}

#[fixture]
fn receipt(
allocation_id: Address,
payer: Address,
data_service: Address,
service_provider: Address,
value: u128,
) -> Receipt {
Receipt::new(allocation_id, payer, data_service, service_provider, value).unwrap()
}

#[rstest]
fn test_new_receipt(allocation_id: Address, value: u128, receipt: Receipt) {
assert_eq!(receipt.allocation_id, allocation_id);
assert_eq!(receipt.value, value);

// Check that the timestamp is within a reasonable range
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Current system time should be greater than `UNIX_EPOCH`")
.as_nanos() as u64;
assert!(receipt.timestamp_ns <= now);
assert!(receipt.timestamp_ns >= now - 5000000); // 5 second tolerance
}

#[rstest]
fn test_unique_nonce_and_timestamp(
#[from(receipt)] receipt1: Receipt,
#[from(receipt)] receipt2: Receipt,
) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Current system time should be greater than `UNIX_EPOCH`")
.as_nanos() as u64;

// Check that nonces are different
// Note: This test has an *extremely low* (~1/2^64) probability of false failure, if a failure happens
// once it is not neccessarily a sign of an issue. If this test fails more than once, especially
// in a short period of time (within a ) then there may be an issue with randomness
// of the nonce generation.
assert_ne!(receipt1.nonce, receipt2.nonce);

assert!(receipt1.timestamp_ns <= now);
assert!(receipt1.timestamp_ns >= now - 5000000); // 5 second tolerance

assert!(receipt2.timestamp_ns <= now);
assert!(receipt2.timestamp_ns >= now - 5000000); // 5 second tolerance
}
}

0 comments on commit e433a8a

Please sign in to comment.