From 191165f39af0b041c57b5f2b53f0355e14093783 Mon Sep 17 00:00:00 2001 From: Arni Hod Date: Thu, 15 Aug 2024 16:36:13 +0300 Subject: [PATCH] refactor: create executable tx in the gateway process_tx --- crates/gateway/src/compilation.rs | 39 ++++++++++++- crates/gateway/src/compilation_test.rs | 11 ++-- crates/gateway/src/gateway.rs | 29 +++++++--- crates/gateway/src/utils.rs | 52 +++++++++++++++++ .../src/executable_transaction.rs | 58 ++++++++++++++++++- 5 files changed, 171 insertions(+), 18 deletions(-) diff --git a/crates/gateway/src/compilation.rs b/crates/gateway/src/compilation.rs index aab1affc40f..5bc70697b7b 100644 --- a/crates/gateway/src/compilation.rs +++ b/crates/gateway/src/compilation.rs @@ -3,8 +3,10 @@ use std::sync::Arc; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_lang_starknet_classes::contract_class::ContractClass as CairoLangContractClass; use starknet_api::contract_class::ClassInfo; -use starknet_api::core::CompiledClassHash; +use starknet_api::core::{ChainId, ClassHash, CompiledClassHash}; +use starknet_api::executable_transaction::DeclareTransaction; use starknet_api::rpc_transaction::RpcDeclareTransaction; +use starknet_api::transaction::{DeclareTransactionV3, TransactionHasher}; use starknet_sierra_compile::cairo_lang_compiler::CairoLangSierraToCasmCompiler; use starknet_sierra_compile::config::SierraToCasmCompilationConfig; use starknet_sierra_compile::utils::into_contract_class_for_compilation; @@ -28,10 +30,11 @@ impl GatewayCompiler { Self { sierra_to_casm_compiler: Arc::new(CairoLangSierraToCasmCompiler { config }) } } + // TODO(Arni): Squash this function into `process_declare_tx`. /// Formats the contract class for compilation, compiles it, and returns the compiled contract /// class wrapped in a [`ClassInfo`]. /// Assumes the contract class is of a Sierra program which is compiled to Casm. - pub fn process_declare_tx( + pub(crate) fn class_info_from_declare_tx( &self, declare_tx: &RpcDeclareTransaction, ) -> GatewayResult { @@ -50,6 +53,38 @@ impl GatewayCompiler { }) } + /// Processes a declare transaction, compiling the contract class and returning the executable + /// declare transaction. + pub fn process_declare_tx( + &self, + rpc_tx: RpcDeclareTransaction, + chain_id: &ChainId, + ) -> GatewayResult { + let class_info = self.class_info_from_declare_tx(&rpc_tx)?; + let RpcDeclareTransaction::V3(tx) = rpc_tx; + let declare_tx = starknet_api::transaction::DeclareTransaction::V3(DeclareTransactionV3 { + class_hash: ClassHash::default(), /* FIXME(yael 15/4/24): call the starknet-api + * function once ready */ + resource_bounds: tx.resource_bounds.into(), + tip: tx.tip, + signature: tx.signature, + nonce: tx.nonce, + compiled_class_hash: tx.compiled_class_hash, + sender_address: tx.sender_address, + nonce_data_availability_mode: tx.nonce_data_availability_mode, + fee_data_availability_mode: tx.fee_data_availability_mode, + paymaster_data: tx.paymaster_data, + account_deployment_data: tx.account_deployment_data, + }); + let tx_hash = declare_tx + .calculate_transaction_hash(chain_id, &declare_tx.version()) + .map_err(|err| { + error!("Failed to calculate tx hash: {}", err); + GatewaySpecError::UnexpectedError { data: "Internal server error.".to_owned() } + })?; + Ok(DeclareTransaction { tx: declare_tx, tx_hash, class_info }) + } + fn compile( &self, cairo_lang_contract_class: CairoLangContractClass, diff --git a/crates/gateway/src/compilation_test.rs b/crates/gateway/src/compilation_test.rs index 8c41913c12f..8d9d56c8470 100644 --- a/crates/gateway/src/compilation_test.rs +++ b/crates/gateway/src/compilation_test.rs @@ -42,7 +42,7 @@ fn test_compile_contract_class_compiled_class_hash_mismatch( declare_tx_v3.compiled_class_hash = wrong_supplied_hash; let declare_tx = RpcDeclareTransaction::V3(declare_tx_v3); - let err = gateway_compiler.process_declare_tx(&declare_tx).unwrap_err(); + let err = gateway_compiler.class_info_from_declare_tx(&declare_tx).unwrap_err(); assert_eq!(err, GatewaySpecError::CompiledClassHashMismatch); assert!(logs_contain( format!( @@ -62,7 +62,8 @@ fn test_compile_contract_class_bytecode_size_validation(declare_tx_v3: RpcDeclar max_bytecode_size: 1, }); - let result = gateway_compiler.process_declare_tx(&RpcDeclareTransaction::V3(declare_tx_v3)); + let result = + gateway_compiler.class_info_from_declare_tx(&RpcDeclareTransaction::V3(declare_tx_v3)); assert_matches!(result.unwrap_err(), GatewaySpecError::CompilationFailed); let expected_compilation_error = CompilationUtilError::CompilationError("Code size limit exceeded.".to_owned()); @@ -80,7 +81,7 @@ fn test_compile_contract_class_bad_sierra( declare_tx_v3.contract_class.sierra_program[..100].to_vec(); let declare_tx = RpcDeclareTransaction::V3(declare_tx_v3); - let err = gateway_compiler.process_declare_tx(&declare_tx).unwrap_err(); + let err = gateway_compiler.class_info_from_declare_tx(&declare_tx).unwrap_err(); assert_eq!(err, GatewaySpecError::CompilationFailed); let expected_compilation_error = @@ -89,7 +90,7 @@ fn test_compile_contract_class_bad_sierra( } #[rstest] -fn test_process_declare_tx_success( +fn test_class_info_from_declare_tx_tx_success( gateway_compiler: GatewayCompiler, declare_tx_v3: RpcDeclareTransactionV3, ) { @@ -98,7 +99,7 @@ fn test_process_declare_tx_success( let abi_length = contract_class.abi.len(); let declare_tx = RpcDeclareTransaction::V3(declare_tx_v3); - let class_info = gateway_compiler.process_declare_tx(&declare_tx).unwrap(); + let class_info = gateway_compiler.class_info_from_declare_tx(&declare_tx).unwrap(); let compiled_class_hash = CompiledClassHash(class_info.casm_contract_class.compiled_class_hash()); assert_eq!(compiled_class_hash, *test_contract_compiled_class_hash()); diff --git a/crates/gateway/src/gateway.rs b/crates/gateway/src/gateway.rs index 7a8ca450932..f474160fb98 100644 --- a/crates/gateway/src/gateway.rs +++ b/crates/gateway/src/gateway.rs @@ -6,7 +6,6 @@ use async_trait::async_trait; use axum::extract::State; use axum::routing::{get, post}; use axum::{Json, Router}; -use blockifier::execution::contract_class::ClassInfo; use starknet_api::executable_transaction::Transaction; use starknet_api::rpc_transaction::RpcTransaction; use starknet_api::transaction::TransactionHash; @@ -23,6 +22,7 @@ use crate::rpc_state_reader::RpcStateReaderFactory; use crate::state_reader::StateReaderFactory; use crate::stateful_transaction_validator::StatefulTransactionValidator; use crate::stateless_transaction_validator::StatelessTransactionValidator; +use crate::utils::compile_to_casm_and_convert_rpc_to_executable_tx; #[cfg(test)] #[path = "gateway_test.rs"] @@ -130,25 +130,36 @@ fn process_tx( // Perform stateless validations. stateless_tx_validator.validate(&tx)?; - // Compile Sierra to Casm. - let optional_class_info = match &tx { - RpcTransaction::Declare(declare_tx) => Some( - ClassInfo::try_from(gateway_compiler.process_declare_tx(declare_tx)?).map_err(|e| { + // TODO(Arni): remove copy_of_rpc_tx and use executable_tx instead. + let copy_of_rpc_tx = tx.clone(); + let executable_tx = compile_to_casm_and_convert_rpc_to_executable_tx( + tx, + &gateway_compiler, + &stateful_tx_validator.config.chain_info.chain_id, + )?; + let optional_class_info = match executable_tx { + starknet_api::executable_transaction::Transaction::Declare(tx) => { + Some(tx.class_info.try_into().map_err(|e| { error!("Failed to convert Starknet API ClassInfo to Blockifier ClassInfo: {:?}", e); GatewaySpecError::UnexpectedError { data: "Internal server error.".to_owned() } - })?, - ), + })?) + } _ => None, }; let validator = stateful_tx_validator.instantiate_validator(state_reader_factory)?; // TODO(Yael 31/7/24): refactor after IntrnalTransaction is ready, delete validate_info and // compute all the info outside of run_validate. - let validate_info = stateful_tx_validator.run_validate(&tx, optional_class_info, validator)?; + let validate_info = + stateful_tx_validator.run_validate(©_of_rpc_tx, optional_class_info, validator)?; // TODO(Arni): Add the Sierra and the Casm to the mempool input. Ok(MempoolInput { - tx: Transaction::new_from_rpc_tx(tx, validate_info.tx_hash, validate_info.sender_address), + tx: Transaction::new_from_rpc_tx( + copy_of_rpc_tx, + validate_info.tx_hash, + validate_info.sender_address, + ), account: Account { sender_address: validate_info.sender_address, state: AccountState { nonce: validate_info.account_nonce }, diff --git a/crates/gateway/src/utils.rs b/crates/gateway/src/utils.rs index fbcb3c507ed..8de9e92bc03 100644 --- a/crates/gateway/src/utils.rs +++ b/crates/gateway/src/utils.rs @@ -6,6 +6,11 @@ use blockifier::transaction::transactions::{ InvokeTransaction as BlockifierInvokeTransaction, }; use starknet_api::core::{calculate_contract_address, ChainId, ContractAddress}; +use starknet_api::executable_transaction::{ + DeployAccountTransaction as ExecutableDeployAccountTransaction, + InvokeTransaction as ExecutableInvokeTransaction, + Transaction as ExecutableTransaction, +}; use starknet_api::rpc_transaction::RpcTransaction; use starknet_api::transaction::{ DeclareTransaction, @@ -15,8 +20,55 @@ use starknet_api::transaction::{ }; use tracing::error; +use crate::compilation::GatewayCompiler; use crate::errors::{GatewaySpecError, StatefulTransactionValidatorResult}; +/// Converts an RPC transaction to an executable transaction. +/// This conversion is dependent on the chain ID. +/// Note, that for declare transaction this step is heavy, as it requires compilation of Sierra to +/// executable contract class. +pub fn compile_to_casm_and_convert_rpc_to_executable_tx( + rpc_tx: RpcTransaction, + gateway_compiler: &GatewayCompiler, + chain_id: &ChainId, +) -> Result { + Ok(match rpc_tx { + RpcTransaction::Declare(rpc_declare_tx) => ExecutableTransaction::Declare( + gateway_compiler.process_declare_tx(rpc_declare_tx, chain_id).map_err(|error| { + error!( + "Failed to convert RPC declare transaction to executable transaction: {}", + error + ); + GatewaySpecError::UnexpectedError { data: "Internal server error".to_owned() } + })?, + ), + RpcTransaction::DeployAccount(rpc_deploy_account_tx) => { + ExecutableTransaction::DeployAccount( + ExecutableDeployAccountTransaction::from_rpc_tx(rpc_deploy_account_tx, chain_id) + .map_err(|error| { + error!( + "Failed to convert RPC deploy account transaction to executable \ + transaction: {}", + error + ); + GatewaySpecError::UnexpectedError { + data: "Internal server error".to_owned(), + } + })?, + ) + } + RpcTransaction::Invoke(rpc_invoke_tx) => ExecutableTransaction::Invoke( + ExecutableInvokeTransaction::from_rpc_tx(rpc_invoke_tx, chain_id).map_err(|error| { + error!( + "Failed to convert RPC invoke transaction to executable transaction: {}", + error + ); + GatewaySpecError::UnexpectedError { data: "Internal server error".to_owned() } + })?, + ), + }) +} + // TODO(Arni): Remove this function. Replace with a function that take ownership of RpcTransaction. pub fn rpc_tx_to_account_tx( rpc_tx: &RpcTransaction, diff --git a/crates/starknet_api/src/executable_transaction.rs b/crates/starknet_api/src/executable_transaction.rs index 6a2246f7d85..73f08eeaf04 100644 --- a/crates/starknet_api/src/executable_transaction.rs +++ b/crates/starknet_api/src/executable_transaction.rs @@ -1,20 +1,24 @@ use serde::{Deserialize, Serialize}; use crate::contract_class::ClassInfo; -use crate::core::{ClassHash, ContractAddress, Nonce}; +use crate::core::{calculate_contract_address, ChainId, ClassHash, ContractAddress, Nonce}; use crate::data_availability::DataAvailabilityMode; -use crate::rpc_transaction::RpcTransaction; +use crate::rpc_transaction::{RpcDeployAccountTransaction, RpcInvokeTransaction, RpcTransaction}; use crate::transaction::{ AccountDeploymentData, Calldata, ContractAddressSalt, + DeployAccountTransactionV3, DeprecatedResourceBoundsMapping, + InvokeTransactionV3, PaymasterData, Tip, TransactionHash, + TransactionHasher, TransactionSignature, TransactionVersion, }; +use crate::StarknetApiError; macro_rules! implement_inner_tx_getter_calls { ($(($field:ident, $field_type:ty)),*) => { @@ -157,6 +161,35 @@ impl DeployAccountTransaction { pub fn tx(&self) -> &crate::transaction::DeployAccountTransaction { &self.tx } + + pub fn from_rpc_tx( + rpc_tx: RpcDeployAccountTransaction, + chain_id: &ChainId, + ) -> Result { + let RpcDeployAccountTransaction::V3(tx) = rpc_tx; + let deploy_account_tx = + crate::transaction::DeployAccountTransaction::V3(DeployAccountTransactionV3 { + resource_bounds: tx.resource_bounds.into(), + tip: tx.tip, + signature: tx.signature, + nonce: tx.nonce, + class_hash: tx.class_hash, + contract_address_salt: tx.contract_address_salt, + constructor_calldata: tx.constructor_calldata, + nonce_data_availability_mode: tx.nonce_data_availability_mode, + fee_data_availability_mode: tx.fee_data_availability_mode, + paymaster_data: tx.paymaster_data, + }); + let contract_address = calculate_contract_address( + deploy_account_tx.contract_address_salt(), + deploy_account_tx.class_hash(), + &deploy_account_tx.constructor_calldata(), + ContractAddress::default(), + )?; + let tx_hash = + deploy_account_tx.calculate_transaction_hash(chain_id, &deploy_account_tx.version())?; + Ok(Self { tx: deploy_account_tx, tx_hash, contract_address }) + } } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -178,4 +211,25 @@ impl InvokeTransaction { pub fn tx(&self) -> &crate::transaction::InvokeTransaction { &self.tx } + + pub fn from_rpc_tx( + rpc_tx: RpcInvokeTransaction, + chain_id: &ChainId, + ) -> Result { + let RpcInvokeTransaction::V3(tx) = rpc_tx; + let invoke_tx = crate::transaction::InvokeTransaction::V3(InvokeTransactionV3 { + resource_bounds: tx.resource_bounds.into(), + tip: tx.tip, + signature: tx.signature, + nonce: tx.nonce, + sender_address: tx.sender_address, + calldata: tx.calldata, + nonce_data_availability_mode: tx.nonce_data_availability_mode, + fee_data_availability_mode: tx.fee_data_availability_mode, + paymaster_data: tx.paymaster_data, + account_deployment_data: tx.account_deployment_data, + }); + let tx_hash = invoke_tx.calculate_transaction_hash(chain_id, &invoke_tx.version())?; + Ok(Self { tx: invoke_tx, tx_hash }) + } }