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

[Test] Execution cost. #2379

Merged
merged 4 commits into from Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
178 changes: 91 additions & 87 deletions synthesizer/process/src/cost.rs
Expand Up @@ -74,97 +74,90 @@ pub fn execution_cost<N: Network>(process: &Process<N>, execution: &Execution<N>
Ok((total_cost, (storage_cost, finalize_cost)))
}

/// Returns the minimum number of microcredits required to run the finalize.
pub fn cost_in_microcredits<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
/// A helper function to determine the plaintext type in bytes.
fn plaintext_size_in_bytes<N: Network>(stack: &Stack<N>, plaintext_type: &PlaintextType<N>) -> Result<u64> {
match plaintext_type {
PlaintextType::Literal(literal_type) => Ok(literal_type.size_in_bytes::<N>() as u64),
PlaintextType::Struct(struct_name) => {
// Retrieve the struct from the stack.
let struct_ = stack.program().get_struct(struct_name)?;
// Retrieve the size of the struct name.
let size_of_name = struct_.name().to_bytes_le()?.len() as u64;
// Retrieve the size of all the members of the struct.
let size_of_members = struct_.members().iter().try_fold(0u64, |acc, (_, member_type)| {
acc.checked_add(plaintext_size_in_bytes(stack, member_type)?).ok_or(anyhow!(
"Overflowed while computing the size of the struct '{}/{struct_name}' - {member_type}",
stack.program_id()
))
})?;
// Return the size of the struct.
Ok(size_of_name.saturating_add(size_of_members))
}
PlaintextType::Array(array_type) => {
// Retrieve the number of elements in the array.
let num_elements = **array_type.length() as u64;
// Compute the size of an array element.
let size_of_element = plaintext_size_in_bytes(stack, array_type.next_element_type())?;
// Return the size of the array.
Ok(num_elements.saturating_mul(size_of_element))
}
}
}

/// A helper function to compute the following: base_cost + (byte_multiplier * size_of_operands).
fn cost_in_size<'a, N: Network>(
stack: &Stack<N>,
finalize: &Finalize<N>,
operands: impl IntoIterator<Item = &'a Operand<N>>,
byte_multiplier: u64,
base_cost: u64,
) -> Result<u64> {
// Retrieve the finalize types.
let finalize_types = stack.get_finalize_types(finalize.name())?;
// Compute the size of the operands.
let size_of_operands = operands.into_iter().try_fold(0u64, |acc, operand| {
// Determine the size of the operand.
let operand_size = match finalize_types.get_type_from_operand(stack, operand)? {
FinalizeType::Plaintext(plaintext_type) => plaintext_size_in_bytes(stack, &plaintext_type)?,
FinalizeType::Future(future) => {
bail!("Future '{future}' is not a valid operand in the finalize scope");
}
};
// Safely add the size to the accumulator.
acc.checked_add(operand_size).ok_or(anyhow!(
"Overflowed while computing the size of the operand '{operand}' in '{}/{}' (finalize)",
stack.program_id(),
finalize.name()
))
})?;
// Return the cost.
Ok(base_cost.saturating_add(byte_multiplier.saturating_mul(size_of_operands)))
}
/// Finalize costs for compute heavy operations, derived as:
/// `BASE_COST + (PER_BYTE_COST * SIZE_IN_BYTES)`.
howardwu marked this conversation as resolved.
Show resolved Hide resolved

// Finalize costs for compute heavy operations, derived as:
// `BASE_COST + (PER_BYTE_COST * SIZE_IN_BYTES)`.
const CAST_BASE_COST: u64 = 500;
const CAST_PER_BYTE_COST: u64 = 30;

const CAST_BASE_COST: u64 = 500;
const CAST_PER_BYTE_COST: u64 = 30;
const HASH_BASE_COST: u64 = 10_000;
const HASH_PER_BYTE_COST: u64 = 30;

const HASH_BASE_COST: u64 = 10_000;
const HASH_PER_BYTE_COST: u64 = 30;
const HASH_BHP_BASE_COST: u64 = 50_000;
const HASH_BHP_PER_BYTE_COST: u64 = 300;

const HASH_BHP_BASE_COST: u64 = 50_000;
const HASH_BHP_PER_BYTE_COST: u64 = 300;
const HASH_PSD_BASE_COST: u64 = 40_000;
const HASH_PSD_PER_BYTE_COST: u64 = 75;

const HASH_PSD_BASE_COST: u64 = 40_000;
const HASH_PSD_PER_BYTE_COST: u64 = 75;
const MAPPING_BASE_COST: u64 = 10_000;
const MAPPING_PER_BYTE_COST: u64 = 10;

const MAPPING_BASE_COST: u64 = 10_000;
const MAPPING_PER_BYTE_COST: u64 = 10;
const SET_BASE_COST: u64 = 10_000;
const SET_PER_BYTE_COST: u64 = 100;

const SET_BASE_COST: u64 = 10_000;
const SET_PER_BYTE_COST: u64 = 100;
/// A helper function to determine the plaintext type in bytes.
fn plaintext_size_in_bytes<N: Network>(stack: &Stack<N>, plaintext_type: &PlaintextType<N>) -> Result<u64> {
match plaintext_type {
PlaintextType::Literal(literal_type) => Ok(literal_type.size_in_bytes::<N>() as u64),
PlaintextType::Struct(struct_name) => {
// Retrieve the struct from the stack.
let struct_ = stack.program().get_struct(struct_name)?;
// Retrieve the size of the struct name.
let size_of_name = struct_.name().to_bytes_le()?.len() as u64;
// Retrieve the size of all the members of the struct.
let size_of_members = struct_.members().iter().try_fold(0u64, |acc, (_, member_type)| {
acc.checked_add(plaintext_size_in_bytes(stack, member_type)?).ok_or(anyhow!(
"Overflowed while computing the size of the struct '{}/{struct_name}' - {member_type}",
stack.program_id()
))
})?;
// Return the size of the struct.
Ok(size_of_name.saturating_add(size_of_members))
}
PlaintextType::Array(array_type) => {
// Retrieve the number of elements in the array.
let num_elements = **array_type.length() as u64;
// Compute the size of an array element.
let size_of_element = plaintext_size_in_bytes(stack, array_type.next_element_type())?;
// Return the size of the array.
Ok(num_elements.saturating_mul(size_of_element))
}
}
}

// Retrieve the finalize logic.
let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else {
// Return a finalize cost of 0, if the function does not have a finalize scope.
return Ok(0);
};
/// A helper function to compute the following: base_cost + (byte_multiplier * size_of_operands).
fn cost_in_size<'a, N: Network>(
stack: &Stack<N>,
finalize: &Finalize<N>,
operands: impl IntoIterator<Item = &'a Operand<N>>,
byte_multiplier: u64,
base_cost: u64,
) -> Result<u64> {
// Retrieve the finalize types.
let finalize_types = stack.get_finalize_types(finalize.name())?;
// Compute the size of the operands.
let size_of_operands = operands.into_iter().try_fold(0u64, |acc, operand| {
// Determine the size of the operand.
let operand_size = match finalize_types.get_type_from_operand(stack, operand)? {
FinalizeType::Plaintext(plaintext_type) => plaintext_size_in_bytes(stack, &plaintext_type)?,
FinalizeType::Future(future) => {
bail!("Future '{future}' is not a valid operand in the finalize scope");
}
};
// Safely add the size to the accumulator.
acc.checked_add(operand_size).ok_or(anyhow!(
"Overflowed while computing the size of the operand '{operand}' in '{}/{}' (finalize)",
stack.program_id(),
finalize.name()
))
})?;
// Return the cost.
Ok(base_cost.saturating_add(byte_multiplier.saturating_mul(size_of_operands)))
}

// Measure the cost of each command.
let cost = |command: &Command<N>| match command {
/// Returns the the cost of a command in a finalize scope.
pub fn cost_per_command<N: Network>(stack: &Stack<N>, finalize: &Finalize<N>, command: &Command<N>) -> Result<u64> {
match command {
Command::Instruction(Instruction::Abs(_)) => Ok(500),
Command::Instruction(Instruction::AbsWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Add(_)) => Ok(500),
Expand Down Expand Up @@ -358,8 +351,16 @@ pub fn cost_in_microcredits<N: Network>(stack: &Stack<N>, function_name: &Identi
}
Command::BranchEq(_) | Command::BranchNeq(_) => Ok(500),
Command::Position(_) => Ok(100),
};
}
}

/// Returns the minimum number of microcredits required to run the finalize.
pub fn cost_in_microcredits<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
// Retrieve the finalize logic.
let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else {
// Return a finalize cost of 0, if the function does not have a finalize scope.
return Ok(0);
};
// Get the cost of finalizing all futures.
let mut future_cost = 0u64;
for input in finalize.inputs() {
Expand All @@ -372,9 +373,12 @@ pub fn cost_in_microcredits<N: Network>(stack: &Stack<N>, function_name: &Identi
.ok_or(anyhow!("Finalize cost overflowed"))?;
}
}

// Aggregate the cost of all commands in the program.
finalize.commands().iter().map(cost).try_fold(future_cost, |acc, res| {
res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed")))
})
finalize
.commands()
.iter()
.map(|command| cost_per_command(stack, finalize, command))
.try_fold(future_cost, |acc, res| {
res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed")))
})
}