Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 16 additions & 2 deletions rs/https_outcalls/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use ic_logger::{ReplicaLogger, info, warn};
use ic_management_canister_types_private::{CanisterHttpResponsePayload, TransformArgs};
use ic_metrics::MetricsRegistry;
use ic_types::{
CanisterId, NumBytes, NumInstructions,
CanisterId, CountBytes, NumBytes, NumInstructions,
canister_http::{
CanisterHttpHeader, CanisterHttpMethod, CanisterHttpPaymentReceipt, CanisterHttpReject,
CanisterHttpRequest, CanisterHttpRequestContext, CanisterHttpResponse,
Expand Down Expand Up @@ -203,7 +203,7 @@ impl NonBlockingChannel<CanisterHttpRequest> for CanisterHttpAdapterClientImpl {

// Only apply the transform if a function name is specified
let transform_timer = metrics.transform_execution_duration.start_timer();
let max_response_size_bytes = budget.get_adapter_limits().max_response_size.get();
let max_response_size_bytes = budget.get_gossip_limit().get();
let transformed_payload = match &request_transform {
Some(transform) => {
let (transform_result, instruction_count) = transform_adapter_response(
Expand Down Expand Up @@ -262,6 +262,20 @@ impl NonBlockingChannel<CanisterHttpRequest> for CanisterHttpAdapterClientImpl {
}
.await;

let payload_size = match &payload {
Ok(data) => data.len(),
Err(reject) => reject.count_bytes(),
};
let payload = match budget.subtract_gossip_usage(payload_size) {
Ok(()) => payload,
Err(PricingError::InsufficientCycles) => {
Err(CanisterHttpReject {
reject_code: RejectCode::SysFatal,
message: "Insufficient cycles".to_string(),
})
}
};

// Create the payment receipt after all processing is complete.
let receipt = budget.create_payment_receipt();

Expand Down
1 change: 1 addition & 0 deletions rs/https_outcalls/pricing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rust_library(
version = "0.1.0",
deps = [
"//rs/config",
"//rs/types/cycles",
"//rs/types/types",
],
)
Expand Down
1 change: 1 addition & 0 deletions rs/https_outcalls/pricing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ documentation.workspace = true

[dependencies]
ic-config = { path = "../../config" }
ic-types-cycles = { path = "../../types/cycles" }
ic-types = { path = "../../types/types" }
30 changes: 28 additions & 2 deletions rs/https_outcalls/pricing/src/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@ use std::time::Duration;
use ic_config::subnet_config::MAX_INSTRUCTIONS_PER_QUERY_MESSAGE;
use ic_types::{
NumBytes, NumInstructions,
canister_http::{CanisterHttpPaymentReceipt, MAX_CANISTER_HTTP_RESPONSE_BYTES},
canister_http::{
CanisterHttpPaymentReceipt, CanisterHttpRequestContext, CanisterHttpResponse,
MAX_CANISTER_HTTP_RESPONSE_BYTES,
},
};
use ic_types_cycles::Cycles;

use crate::{AdapterLimits, BudgetTracker, NetworkUsage, PricingError};
use crate::{AdapterLimits, BudgetTracker, NetworkUsage, PricingCalculator, PricingError};

pub struct LegacyCalculator;

impl PricingCalculator for LegacyCalculator {
fn consensus_cost(&self, _response: &CanisterHttpResponse, _subnet_size: u32) -> Cycles {
// Note: the legacy pricing calculator does not calculate the cost of the response.
Cycles::zero()
}

fn base_cost(&self, _context: &CanisterHttpRequestContext, _subnet_size: u32) -> Cycles {
// Note: the legacy pricing calculator does not calculate the cost of the request.
Cycles::zero()
}
}

pub struct LegacyTracker {
max_response_size: NumBytes,
Expand Down Expand Up @@ -45,6 +63,14 @@ impl BudgetTracker for LegacyTracker {
Ok(())
}

fn get_gossip_limit(&self) -> NumBytes {
self.max_response_size
}

fn subtract_gossip_usage(&mut self, _response_size: usize) -> Result<(), PricingError> {
Ok(())
}

fn create_payment_receipt(&self) -> CanisterHttpPaymentReceipt {
// Legacy pricing does not perform cycles accounting, so no cycles
// are ever refunded.
Expand Down
33 changes: 32 additions & 1 deletion rs/https_outcalls/pricing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,26 @@ use std::time::Duration;

use ic_types::{
NumBytes, NumInstructions,
canister_http::{CanisterHttpPaymentReceipt, CanisterHttpRequestContext},
canister_http::{CanisterHttpPaymentReceipt, CanisterHttpRequestContext, CanisterHttpResponse},
};
use ic_types_cycles::Cycles;
use legacy::LegacyTracker;

use crate::legacy::LegacyCalculator;

pub trait PricingCalculator {
/// Returns the number of cycles that the replica should charge for the given HTTP response,
/// in order to include it in a block payload.
fn consensus_cost(&self, response: &CanisterHttpResponse, subnet_size: u32) -> Cycles;

/// Returns the base cost that should be charged for the given HTTP request.
fn base_cost(&self, context: &CanisterHttpRequestContext, subnet_size: u32) -> Cycles;
}

pub trait BudgetTracker: Send {
/// Returns the maximum network resources the Adapter is allowed to consume.
fn get_adapter_limits(&self) -> AdapterLimits;

/// Deducts the actual network resources consumed.
///
/// # Invariants
Expand All @@ -19,13 +32,25 @@ pub trait BudgetTracker: Send {
///
/// Note that "<=" is used here to mean field-wise less than or equal to.
fn subtract_network_usage(&mut self, network_usage: NetworkUsage) -> Result<(), PricingError>;

/// Returns the maximum instructions allowed for the transformation function.
fn get_transform_limit(&self) -> NumInstructions;

/// Deducts the actual instructions consumed by the transformation.
///
/// # Invariants
/// - This method returns `Ok(())` if and only if `usage <= get_transform_limit()`.
fn subtract_transform_usage(&mut self, usage: NumInstructions) -> Result<(), PricingError>;

/// Returns the maximum number of bytes the response may have in order to be gossiped.
fn get_gossip_limit(&self) -> NumBytes;

/// Deducts the actual number of bytes of the response that were gossiped.
///
/// # Invariants
/// - This method returns `Ok(())` if and only if `response_size <= get_gossip_limit()`.
fn subtract_gossip_usage(&mut self, response_size: usize) -> Result<(), PricingError>;

/// Produces the per-replica payment receipt that summarizes the cycles
/// accounting outcome of the outcall, given the resources consumed so
/// far via the `subtract_*` methods.
Expand Down Expand Up @@ -58,4 +83,10 @@ impl PricingFactory {
// Currently, we only support the legacy pricing version.
Box::new(LegacyTracker::new(context.max_response_bytes))
}

pub fn new_calculator(_context: &CanisterHttpRequestContext) -> Box<dyn PricingCalculator> {
// TODO(IC-1937): This should take into account context.pricing_version and a replica config.
// Currently, we only support the legacy pricing version.
Box::new(LegacyCalculator {})
}
}
Loading