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

Feature: support proving blocks with DEPLOY_ACCOUNT transactions #362

Merged
merged 12 commits into from
Sep 10, 2024
9 changes: 5 additions & 4 deletions crates/bin/prove_block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ fn compute_class_commitment(
class_proofs: &HashMap<Felt, PathfinderClassProof>,
) -> CommitmentInfo {
for (class_hash, previous_class_proof) in previous_class_proofs {
assert!(previous_class_proof.verify(*class_hash).is_ok());
previous_class_proof.verify(*class_hash).expect("Could not verify previous_class_proof");
}

for (class_hash, class_proof) in class_proofs {
assert!(class_proof.verify(*class_hash).is_ok());
class_proof.verify(*class_hash).expect("Could not verify class_proof");
}

let previous_class_proofs: Vec<_> = previous_class_proofs.values().cloned().collect();
Expand Down Expand Up @@ -257,8 +257,9 @@ pub async fn prove_block(
// block, likely for contracts that are deployed in this block
let class_proofs =
get_class_proofs(&rpc_client, block_number, &class_hashes[..]).await.expect("Failed to fetch class proofs");
notlesh marked this conversation as resolved.
Show resolved Hide resolved
let previous_class_proofs =
get_class_proofs(&rpc_client, block_number - 1, &class_hashes[..]).await.expect("Failed to fetch class proofs");
let previous_class_proofs = get_class_proofs(&rpc_client, block_number - 1, &class_hashes[..])
.await
.expect("Failed to fetch previous class proofs");

let visited_pcs: HashMap<Felt252, Vec<Felt252>> = blockifier_state
.visited_pcs
Expand Down
26 changes: 23 additions & 3 deletions crates/bin/prove_block/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use cairo_vm::Felt252;
use rpc_replay::transactions::resource_bounds_core_to_api;
use starknet::core::types::{
Expand All @@ -6,6 +8,8 @@ use starknet::core::types::{
InvokeTransaction, InvokeTransactionV0, InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction,
Transaction,
};
use starknet_api::core::{calculate_contract_address, ClassHash};
use starknet_api::transaction::{Calldata, ContractAddressSalt};
use starknet_os::io::InternalTransaction;

// entry point for "__execute__"
Expand Down Expand Up @@ -156,15 +160,29 @@ fn declare_v3_to_internal_tx(input: DeclareTransactionV3) -> InternalTransaction
}

fn deploy_account_v1_to_internal_tx(input: DeployAccountTransactionV1) -> InternalTransaction {
let entry_point_selector = Some(Felt252::ZERO);
InternalTransaction {
hash_value: input.transaction_hash,
max_fee: Some(input.max_fee),
signature: Some(input.signature.into_iter().map(Felt252::from).collect()),
nonce: Some(input.nonce),
contract_address_salt: Some(input.contract_address_salt),
constructor_calldata: Some(input.constructor_calldata.into_iter().map(Felt252::from).collect()),
constructor_calldata: Some(input.constructor_calldata.clone()),
class_hash: Some(input.class_hash),
r#type: "DEPLOY_ACCOUNT".to_string(),
version: Some(Felt252::ONE),
entry_point_selector,
contract_address: Some(
*calculate_contract_address(
ContractAddressSalt(input.contract_address_salt),
ClassHash(input.class_hash),
&Calldata(Arc::new(input.constructor_calldata)),
Default::default(),
)
.unwrap()
.0
.key(),
),
..Default::default()
}
}
Expand All @@ -183,6 +201,7 @@ pub fn deploy_account_v3_to_internal_tx(input: DeployAccountTransactionV3) -> In
nonce_data_availability_mode: Some(da_to_felt(input.nonce_data_availability_mode)),
fee_data_availability_mode: Some(da_to_felt(input.fee_data_availability_mode)),
r#type: "DEPLOY_ACCOUNT".to_string(),
version: Some(Felt252::THREE),
..Default::default()
}
}
Expand Down Expand Up @@ -445,12 +464,13 @@ mod tests {
);
assert_eq!(result.class_hash, Some(input.class_hash));
assert_eq!(result.r#type, "DEPLOY_ACCOUNT".to_string());
assert!(result.contract_address.is_some());
assert_eq!(result.entry_point_selector, Some(Felt::ZERO));

// Check defaulted fields
assert_eq!(result.contract_address, None);
assert_eq!(result.contract_hash, None);
assert_eq!(result.compiled_class_hash, None);
assert_eq!(result.entry_point_selector, None);

assert_eq!(result.tip, None);
assert_eq!(result.resource_bounds, None);
assert_eq!(result.paymaster_data, None);
Expand Down
14 changes: 13 additions & 1 deletion crates/bin/prove_block/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,19 @@ pub(crate) fn get_subcalled_contracts_from_tx_traces(
);
}

_ => unimplemented!("process other txn traces"),
TransactionTrace::DeployAccount(deploy_trace) => {
if let Some(inv) = &deploy_trace.validate_invocation {
process_function_invocations(inv, &mut contracts_subcalled, &mut classes_subcalled);
}
if let Some(inv) = &deploy_trace.fee_transfer_invocation {
process_function_invocations(inv, &mut contracts_subcalled, &mut classes_subcalled);
}
process_function_invocations(
&deploy_trace.constructor_invocation,
&mut contracts_subcalled,
&mut classes_subcalled,
);
}
}
}
(contracts_subcalled, classes_subcalled)
Expand Down
4 changes: 4 additions & 0 deletions crates/bin/prove_block/tests/prove_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rstest::rstest;
// # * 87019: diff assert values in contract subcall
// # * 90000: one of the subcalls results in a call to `replace_class()`.
// # * 87041: block with nonce bump inconsistency
// # * 97581, 101556, 102076 deploy account txns
#[rstest]
#[case::small_block_with_only_invoke_txs(76793)]
#[case::additional_basic_blocks_1(76766)]
Expand All @@ -24,6 +25,9 @@ use rstest::rstest;
#[case::invoke_with_call_to_deploy_syscall(124534)]
#[case::block_with_nonce_bump_inconsistency(87041)]
#[case::declare_tx(76840)]
#[case::deploy_account_v1(97581)]
#[case::deploy_account_v3(101556)]
#[case::deploy_account_many_txs(102076)]
#[ignore = "Requires a running Pathfinder node"]
#[tokio::test(flavor = "multi_thread")]
async fn test_prove_selected_blocks(#[case] block_number: u64) {
Expand Down
13 changes: 6 additions & 7 deletions crates/rpc-replay/src/rpc_state_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,12 @@ impl AsyncRpcStateReader {
}

pub async fn get_nonce_at_async(&self, contract_address: ContractAddress) -> StateResult<Nonce> {
let nonce = self
.rpc_client
.starknet_rpc()
.get_nonce(self.block_id, *contract_address.key())
.await
.map_err(provider_error_to_state_error)?;

let res = self.rpc_client.starknet_rpc().get_nonce(self.block_id, *contract_address.key()).await;
let nonce = match res {
Ok(value) => Ok(value),
Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => Ok(Felt::ZERO),
Err(e) => Err(provider_error_to_state_error(e)),
}?;
Ok(Nonce(nonce))
}

Expand Down
106 changes: 99 additions & 7 deletions crates/rpc-replay/src/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use blockifier::transaction::account_transaction::AccountTransaction;
use blockifier::transaction::errors::TransactionExecutionError;
use rpc_client::RpcClient;
use starknet::core::types::{
BlockId, DeclareTransaction, DeclareTransactionV1, DeclareTransactionV2, DeclareTransactionV3, Felt,
InvokeTransaction, InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, ResourceBoundsMapping,
Transaction, TransactionTrace, TransactionTraceWithHash,
BlockId, DeclareTransaction, DeclareTransactionV1, DeclareTransactionV2, DeclareTransactionV3,
DeployAccountTransaction, DeployAccountTransactionV1, DeployAccountTransactionV3, Felt, InvokeTransaction,
InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, ResourceBoundsMapping, Transaction,
TransactionTrace, TransactionTraceWithHash,
};
use starknet::providers::{Provider, ProviderError};
use starknet_api::core::PatriciaKey;
use starknet_api::core::{calculate_contract_address, ContractAddress, PatriciaKey};
use starknet_api::transaction::{Fee, TransactionHash};
use starknet_api::StarknetApiError;
use starknet_os_types::deprecated_compiled_class::GenericDeprecatedCompiledClass;
Expand Down Expand Up @@ -252,6 +253,93 @@ fn l1_handler_to_blockifier(
Ok(blockifier::transaction::transaction_execution::Transaction::L1HandlerTransaction(l1_handler))
}

/// Calculates a contract address for deploy transaction
fn recalculate_contract_address(
api_tx: &starknet_api::transaction::DeployAccountTransaction,
) -> Result<ContractAddress, StarknetApiError> {
calculate_contract_address(
api_tx.contract_address_salt(),
api_tx.class_hash(),
&api_tx.constructor_calldata(),
// When the contract is deployed via a DEPLOY_ACCOUNT transaction: 0
ContractAddress::from(0_u8),
)
}

fn deploy_account_v1_to_blockifier(
tx: &DeployAccountTransactionV1,
) -> Result<blockifier::transaction::transaction_execution::Transaction, StarknetApiError> {
let tx_hash = TransactionHash(tx.transaction_hash);

let (max_fee, signature, nonce, class_hash, constructor_calldata, contract_address_salt) = (
Fee(felt_to_u128(&tx.max_fee)),
starknet_api::transaction::TransactionSignature(tx.signature.to_vec()),
starknet_api::core::Nonce(tx.nonce),
starknet_api::core::ClassHash(tx.class_hash),
starknet_api::transaction::Calldata(Arc::new(tx.constructor_calldata.to_vec())),
starknet_api::transaction::ContractAddressSalt(tx.contract_address_salt),
);
let contract_address = calculate_contract_address(
contract_address_salt,
class_hash,
&constructor_calldata,
ContractAddress::default(),
)
.unwrap();
let api_tx = starknet_api::transaction::DeployAccountTransaction::V1(
starknet_api::transaction::DeployAccountTransactionV1 {
max_fee,
signature,
nonce,
class_hash,
constructor_calldata,
contract_address_salt,
},
);

let deploy = blockifier::transaction::transactions::DeployAccountTransaction {
tx: api_tx,
tx_hash,
only_query: false,
contract_address,
};

Ok(blockifier::transaction::transaction_execution::Transaction::AccountTransaction(
AccountTransaction::DeployAccount(deploy),
))
}

fn deploy_account_v3_to_blockifier(
tx: &DeployAccountTransactionV3,
) -> Result<blockifier::transaction::transaction_execution::Transaction, StarknetApiError> {
let tx_hash = TransactionHash(tx.transaction_hash);
let api_tx = starknet_api::transaction::DeployAccountTransaction::V3(
starknet_api::transaction::DeployAccountTransactionV3 {
resource_bounds: resource_bounds_core_to_api(&tx.resource_bounds),
tip: starknet_api::transaction::Tip(tx.tip),
signature: starknet_api::transaction::TransactionSignature(tx.signature.clone()),
nonce: starknet_api::core::Nonce(tx.nonce),
class_hash: starknet_api::core::ClassHash(tx.class_hash),
contract_address_salt: starknet_api::transaction::ContractAddressSalt(tx.contract_address_salt),
constructor_calldata: starknet_api::transaction::Calldata(Arc::new(tx.constructor_calldata.clone())),
nonce_data_availability_mode: da_mode_core_to_api(tx.nonce_data_availability_mode),
fee_data_availability_mode: da_mode_core_to_api(tx.fee_data_availability_mode),
paymaster_data: starknet_api::transaction::PaymasterData(tx.paymaster_data.clone()),
},
);
let contract_address = recalculate_contract_address(&api_tx)?;
let deploy_account = blockifier::transaction::transactions::DeployAccountTransaction {
tx: api_tx,
tx_hash,
contract_address,
only_query: false,
};

Ok(blockifier::transaction::transaction_execution::Transaction::AccountTransaction(
AccountTransaction::DeployAccount(deploy_account),
))
}

/// Maps starknet-core transactions to Blockifier-compatible types.
pub async fn starknet_rs_to_blockifier(
sn_core_tx: &starknet::core::types::Transaction,
Expand All @@ -273,10 +361,14 @@ pub async fn starknet_rs_to_blockifier(
DeclareTransaction::V3(tx) => declare_v3_to_blockifier(tx, client, block_number).await?,
},
Transaction::L1Handler(tx) => l1_handler_to_blockifier(tx, trace, gas_prices)?,
Transaction::DeployAccount(_tx) => {
unimplemented!("starknet_rs_tx_to_blockifier() with Deploy txn");
Transaction::DeployAccount(tx) => match tx {
DeployAccountTransaction::V1(tx) => deploy_account_v1_to_blockifier(tx)?,
DeployAccountTransaction::V3(tx) => deploy_account_v3_to_blockifier(tx)?,
},

Transaction::Deploy(_) => {
unimplemented!("we do not plan to support deprecated deploy txs, only deploy_account")
}
_ => unimplemented!(),
};

Ok(blockifier_tx)
Expand Down
Loading