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(blockifier): support reverted sierra gas tracking and charging #3011

Closed
Closed
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
2 changes: 2 additions & 0 deletions crates/blockifier/src/blockifier/stateful_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Arc;

use starknet_api::core::{ContractAddress, Nonce};
use starknet_api::executable_transaction::AccountTransaction as ApiTransaction;
use starknet_api::execution_resources::GasAmount;
use thiserror::Error;

use crate::blockifier::config::TransactionExecutorConfig;
Expand Down Expand Up @@ -132,6 +133,7 @@ impl<S: StateReader> StatefulValidator<S> {
&tx_context.block_context.versioned_constants,
),
0,
GasAmount(0),
);

Ok((validate_call_info, tx_receipt))
Expand Down
81 changes: 78 additions & 3 deletions crates/blockifier/src/execution/entry_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ impl CallEntryPoint {
if let Ok(call_info) = &execution_result {
// If the execution of the outer call failed, revert the transction.
if call_info.execution.failed {
// For Cairo1 revert, need to explicitly set the amount of gas to revert from the
// call info.
context
.sierra_gas_revert_tracker
.set_gas_consumed_directly(GasAmount(call_info.execution.gas_consumed));
return Err(EntryPointExecutionError::ExecutionFailed {
error_trace: extract_trailing_cairo1_revert_trace(
call_info,
Expand All @@ -183,6 +188,7 @@ impl CallEntryPoint {

execution_result
}

pub fn verify_constructor(&self) -> Result<(), PreExecutionError> {
if self.entry_point_type == EntryPointType::Constructor
&& self.entry_point_selector != selector_from_name(CONSTRUCTOR_ENTRY_POINT_NAME)
Expand All @@ -202,6 +208,55 @@ pub struct ConstructorContext {
pub caller_address: ContractAddress,
}

#[derive(Debug)]
pub struct SierraGasRevertTracker {
sierra_gas_consumed_so_far: GasAmount,
last_seen_remaining_gas: GasAmount,
}

impl SierraGasRevertTracker {
pub fn new_execute(initial_remaining_gas: GasAmount) -> Self {
Self {
sierra_gas_consumed_so_far: GasAmount(0),
last_seen_remaining_gas: initial_remaining_gas,
}
}

/// In validate, we cannot charge for reverted gas, so the values are meaningless.
pub fn new_validate() -> Self {
Self::new_execute(GasAmount::MAX)
}

/// Given the next remaining gas (before entering the next syscall), updates the consumed gas
/// (difference between previous and next remaining gas) and then updates the last seen
/// remaining gas.
pub fn update_before_syscall(&mut self, next_remaining_gas: GasAmount) {
let next_gas_consumed =
self.last_seen_remaining_gas.checked_sub(next_remaining_gas).expect(&format!(
"Next remaining gas higher than last. Last seen remaining gas: {}, next remaining \
gas: {next_remaining_gas}.",
self.last_seen_remaining_gas
));
self.sierra_gas_consumed_so_far =
self.sierra_gas_consumed_so_far.checked_add(next_gas_consumed).expect(&format!(
"Sierra gas consumed overflowed. Last consumed gas: {}, extra consumed gas: \
{next_gas_consumed}.",
self.sierra_gas_consumed_so_far
));
self.last_seen_remaining_gas = next_remaining_gas;
}

/// In Cairo1 reverts, the total gas consumed for fee charge is available on the call info, and
/// can be used directly.
pub fn set_gas_consumed_directly(&mut self, gas_consumed: GasAmount) {
self.sierra_gas_consumed_so_far = gas_consumed;
}

pub fn get_gas_consumed(&self) -> GasAmount {
self.sierra_gas_consumed_so_far
}
}

#[derive(Debug)]
pub struct EntryPointExecutionContext {
// We use `Arc` to avoid the clone of this potentially large object, as inner calls
Expand All @@ -223,13 +278,17 @@ pub struct EntryPointExecutionContext {

// Information for reverting the state (inludes the revert info of the callers).
pub revert_infos: ExecutionRevertInfo,

// Used to support charging for gas consumed in blockifier revert flow.
pub sierra_gas_revert_tracker: SierraGasRevertTracker,
}

impl EntryPointExecutionContext {
pub fn new(
tx_context: Arc<TransactionContext>,
mode: ExecutionMode,
limit_steps_by_resources: bool,
sierra_gas_revert_tracker: SierraGasRevertTracker,
) -> Self {
let max_steps = Self::max_steps(&tx_context, &mode, limit_steps_by_resources);
Self {
Expand All @@ -241,18 +300,33 @@ impl EntryPointExecutionContext {
execution_mode: mode,
tracked_resource_stack: vec![],
revert_infos: ExecutionRevertInfo(vec![]),
sierra_gas_revert_tracker,
}
}

pub fn new_validate(
tx_context: Arc<TransactionContext>,
limit_steps_by_resources: bool,
) -> Self {
Self::new(tx_context, ExecutionMode::Validate, limit_steps_by_resources)
Self::new(
tx_context,
ExecutionMode::Validate,
limit_steps_by_resources,
SierraGasRevertTracker::new_validate(),
)
}

pub fn new_invoke(tx_context: Arc<TransactionContext>, limit_steps_by_resources: bool) -> Self {
Self::new(tx_context, ExecutionMode::Execute, limit_steps_by_resources)
pub fn new_invoke(
tx_context: Arc<TransactionContext>,
limit_steps_by_resources: bool,
sierra_gas_revert_tracker: SierraGasRevertTracker,
) -> Self {
Self::new(
tx_context,
ExecutionMode::Execute,
limit_steps_by_resources,
sierra_gas_revert_tracker,
)
}

/// Returns the maximum number of cairo steps allowed, given the max fee, gas price and the
Expand Down Expand Up @@ -428,6 +502,7 @@ pub fn execute_constructor_entry_point(
initial_gas: *remaining_gas,
};

// TODO: On error, compute sierra gas consumed and charge for blockifier revert flow.
constructor_call.non_reverting_execute(state, context, remaining_gas).map_err(|error| {
ConstructorEntryPointExecutionError::new(error, &ctor_context, Some(constructor_selector))
})
Expand Down
22 changes: 22 additions & 0 deletions crates/blockifier/src/execution/syscalls/hint_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use cairo_vm::vm::errors::vm_errors::VirtualMachineError;
use cairo_vm::vm::runners::cairo_runner::{ResourceTracker, RunResources};
use cairo_vm::vm::vm_core::VirtualMachine;
use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector};
use starknet_api::execution_resources::GasAmount;
use starknet_api::transaction::fields::{
AllResourceBounds,
Calldata,
Expand All @@ -26,6 +27,7 @@ use thiserror::Error;

use crate::abi::sierra_types::SierraTypeError;
use crate::execution::common_hints::{ExecutionMode, HintExecutionResult};
use crate::execution::contract_class::TrackedResource;
use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext};
use crate::execution::errors::{ConstructorEntryPointExecutionError, EntryPointExecutionError};
use crate::execution::execution_utils::{
Expand Down Expand Up @@ -475,6 +477,26 @@ impl<'a> SyscallHintProcessor<'a> {

// Execute.
let mut remaining_gas = gas_counter - required_gas;

// To support sierra gas charge for blockifier revert flow, we track the remaining gas left
// before executing a syscall if the current tracked resource is gas.
// 1. If the syscall does not run Cairo code (i.e. not library call, not call contract, and
// not a deploy), any failure will not run in the OS, so no need to charge - the value
// before entering the callback is good enough to charge.
// 2. If the syscall runs Cairo code, but the tracked resource is steps (and not gas), the
// additional charge of reverted cairo steps will cover the inner cost, and the outer
// cost we track here will be the additional reverted gas.
// 3. If the syscall runs Cairo code and the tracked resource is gas, either the inner
// failure will be a Cairo1 revert (and the gas consumed on the call info will override
// the current tracked value), or we will pass through another syscall before failing -
// and by induction (we will reach this point again), the gas will be charged correctly.
if self.base.context.tracked_resource_stack.last() == Some(&TrackedResource::CairoSteps) {
self.base
.context
.sierra_gas_revert_tracker
.update_before_syscall(GasAmount(remaining_gas));
}

let original_response = execute_callback(request, vm, self, &mut remaining_gas);
let response = match original_response {
Ok(response) => {
Expand Down
7 changes: 6 additions & 1 deletion crates/blockifier/src/fee/receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct TransactionReceiptParameters<'a> {
execution_summary_without_fee_transfer: ExecutionSummary,
tx_type: TransactionType,
reverted_steps: usize,
reverted_sierra_gas: GasAmount,
}

// TODO(Gilad): Use everywhere instead of passing the `actual_{fee,resources}` tuple, which often
Expand Down Expand Up @@ -58,6 +59,7 @@ impl TransactionReceipt {
execution_summary_without_fee_transfer,
tx_type,
reverted_steps,
reverted_sierra_gas,
} = tx_receipt_params;
let charged_resources = execution_summary_without_fee_transfer.charged_resources.clone();
let starknet_resources = StarknetResources::new(
Expand Down Expand Up @@ -85,7 +87,7 @@ impl TransactionReceipt {
vm_resources: total_vm_resources,
n_reverted_steps: reverted_steps,
sierra_gas: charged_resources.gas_for_fee,
reverted_sierra_gas: GasAmount(0), // TODO(tzahi): compute value.
reverted_sierra_gas,
},
};

Expand Down Expand Up @@ -127,6 +129,7 @@ impl TransactionReceipt {
execution_summary_without_fee_transfer,
tx_type: TransactionType::L1Handler,
reverted_steps: 0,
reverted_sierra_gas: GasAmount(0),
})
}

Expand All @@ -137,6 +140,7 @@ impl TransactionReceipt {
state_changes: &'a StateChanges,
execution_summary_without_fee_transfer: ExecutionSummary,
reverted_steps: usize,
reverted_sierra_gas: GasAmount,
) -> Self {
Self::from_params(TransactionReceiptParameters {
tx_context,
Expand All @@ -149,6 +153,7 @@ impl TransactionReceipt {
execution_summary_without_fee_transfer,
tx_type: account_tx.tx_type(),
reverted_steps,
reverted_sierra_gas,
})
}
}
9 changes: 8 additions & 1 deletion crates/blockifier/src/test_utils/prices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
use starknet_api::abi::abi_utils::{get_fee_token_var_address, selector_from_name};
use starknet_api::block::FeeType;
use starknet_api::core::ContractAddress;
use starknet_api::execution_resources::GasAmount;
use starknet_api::test_utils::invoke::InvokeTxArgs;
use starknet_api::transaction::constants;
use starknet_api::{calldata, felt};

use crate::context::BlockContext;
use crate::execution::common_hints::ExecutionMode;
use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext};
use crate::execution::entry_point::{
CallEntryPoint,
EntryPointExecutionContext,
SierraGasRevertTracker,
};
use crate::state::state_api::State;
use crate::test_utils::initial_test_state::test_state;
use crate::test_utils::BALANCE;
Expand Down Expand Up @@ -77,6 +82,8 @@ fn fee_transfer_resources(
),
ExecutionMode::Execute,
false,
// No need to limit gas in fee transfer.
SierraGasRevertTracker::new_execute(GasAmount::MAX),
),
&mut remaining_gas,
)
Expand Down
3 changes: 3 additions & 0 deletions crates/blockifier/src/test_utils/struct_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use starknet_api::contract_address;
use starknet_api::contract_class::SierraVersion;
use starknet_api::core::{ChainId, ClassHash};
use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass;
use starknet_api::execution_resources::GasAmount;
use starknet_api::test_utils::{TEST_ERC20_CONTRACT_ADDRESS, TEST_ERC20_CONTRACT_ADDRESS2};

use crate::bouncer::{BouncerConfig, BouncerWeights, BuiltinCount};
Expand All @@ -28,6 +29,7 @@ use crate::execution::entry_point::{
CallEntryPoint,
EntryPointExecutionContext,
EntryPointExecutionResult,
SierraGasRevertTracker,
};
#[cfg(feature = "cairo_native")]
use crate::execution::native::contract_class::NativeCompiledClassV1;
Expand Down Expand Up @@ -72,6 +74,7 @@ impl CallEntryPoint {
Arc::new(tx_context),
execution_mode,
limit_steps_by_resources,
SierraGasRevertTracker::new_execute(GasAmount(self.initial_gas)),
);
let mut remaining_gas = self.initial_gas;
self.execute(state, &mut context, &mut remaining_gas)
Expand Down
Loading
Loading