Skip to content

Commit

Permalink
feat: ✨ add_*_transaction
Browse files Browse the repository at this point in the history
send the execution request to the sequencer, add error codes
  • Loading branch information
jbcaron committed Feb 29, 2024
1 parent 9cb45f1 commit fbfb06e
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 76 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ git # Madara Changelog

## Next release

- feat(rpc): add_invoke_tx, add_deploy_account_tx, add_declare_tx
- feat(rpc): tx_receipt, re-execute tx
- feat(script): added CI scripts for starting Deoxys and comparing JSON RPC
calls
Expand Down
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.

1 change: 1 addition & 0 deletions crates/client/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ blockifier = { workspace = true, default-features = true }
starknet-core = { workspace = true }
starknet-ff = { workspace = true }
starknet_api = { workspace = true, default-features = true }
starknet-providers = { workspace = true }
# Others
anyhow = { workspace = true }
hex = { workspace = true, default-features = true }
Expand Down
62 changes: 61 additions & 1 deletion crates/client/rpc/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use jsonrpsee::types::error::{CallError, ErrorObject};
use pallet_starknet_runtime_api::StarknetTransactionExecutionError;
use starknet_core::types::StarknetError;

// Comes from the RPC Spec:
// https://github.com/starkware-libs/starknet-specs/blob/0e859ff905795f789f1dfd6f7340cdaf5015acc8/api/starknet_write_api.json#L227
Expand All @@ -11,6 +12,10 @@ pub enum StarknetRpcApiError {
ContractNotFound = 20,
#[error("Block not found")]
BlockNotFound = 24,
#[error("Invalid transaction hash")]
InvalidTxnHash = 25,
#[error("Invalid tblock hash")]
InvalidBlockHash = 26,
#[error("Invalid transaction index in a block")]
InvalidTxnIndex = 27,
#[error("Class hash not found")]
Expand All @@ -29,14 +34,36 @@ pub enum StarknetRpcApiError {
FailedToFetchPendingTransactions = 38,
#[error("Contract error")]
ContractError = 40,
#[error("Transaction execution error")]
TxnExecutionError = 41,
#[error("Invalid contract class")]
InvalidContractClass = 50,
#[error("Class already declared")]
ClassAlreadyDeclared = 51,
#[error("Invalid transaction nonce")]
InvalidTxnNonce = 52,
#[error("Max fee is smaller than the minimal transaction cost (validation plus fee transfer)")]
InsufficientMaxFee = 53,
#[error("Account balance is smaller than the transaction's max_fee")]
InsufficientAccountBalance = 54,
#[error("Account validation failed")]
ValidationFailure = 55,
#[error("Compilation failed")]
CompilationFailed = 56,
#[error("Contract class size is too large")]
ContractClassSizeTooLarge = 57,
#[error("Sender address is not an account contract")]
NonAccount = 58,
#[error("A transaction with the same hash already exists in the mempool")]
DuplicateTxn = 59,
#[error("The compiled class hash did not match the one supplied in the transaction")]
CompiledClassHashMismatch = 60,
#[error("The transaction version is not supported")]
UnsupportedTxVersion = 61,
UnsupportedTxnVersion = 61,
#[error("The contract class version is not supported")]
UnsupportedContractClassVersion = 62,
#[error("An unexpected error occurred")]
ErrUnexpectedError = 63,
#[error("Internal server error")]
InternalServerError = 500,
#[error("Unimplemented method")]
Expand All @@ -62,3 +89,36 @@ impl From<StarknetRpcApiError> for jsonrpsee::core::Error {
jsonrpsee::core::Error::Call(CallError::Custom(ErrorObject::owned(err as i32, err.to_string(), None::<()>)))
}
}

impl From<StarknetError> for StarknetRpcApiError {
fn from(err: StarknetError) -> Self {
match err {
StarknetError::FailedToReceiveTransaction => StarknetRpcApiError::FailedToReceiveTxn,
StarknetError::ContractNotFound => StarknetRpcApiError::ContractNotFound,
StarknetError::BlockNotFound => StarknetRpcApiError::BlockNotFound,
StarknetError::InvalidTransactionIndex => StarknetRpcApiError::InvalidTxnIndex,
StarknetError::ClassHashNotFound => StarknetRpcApiError::ClassHashNotFound,
StarknetError::TransactionHashNotFound => StarknetRpcApiError::TxnHashNotFound,
StarknetError::PageSizeTooBig => StarknetRpcApiError::PageSizeTooBig,
StarknetError::NoBlocks => StarknetRpcApiError::NoBlocks,
StarknetError::InvalidContinuationToken => StarknetRpcApiError::InvalidContinuationToken,
StarknetError::TooManyKeysInFilter => StarknetRpcApiError::TooManyKeysInFilter,
StarknetError::ContractError(_) => StarknetRpcApiError::ContractError,
StarknetError::ClassAlreadyDeclared => StarknetRpcApiError::ClassAlreadyDeclared,
StarknetError::InvalidTransactionNonce => StarknetRpcApiError::InvalidTxnNonce,
StarknetError::InsufficientMaxFee => StarknetRpcApiError::InsufficientMaxFee,
StarknetError::InsufficientAccountBalance => StarknetRpcApiError::InsufficientAccountBalance,
StarknetError::ValidationFailure => StarknetRpcApiError::ValidationFailure,
StarknetError::CompilationFailed => StarknetRpcApiError::CompilationFailed,
StarknetError::ContractClassSizeIsTooLarge => StarknetRpcApiError::ContractClassSizeTooLarge,
StarknetError::NonAccount => StarknetRpcApiError::NonAccount,
StarknetError::DuplicateTx => StarknetRpcApiError::DuplicateTxn,
StarknetError::CompiledClassHashMismatch => StarknetRpcApiError::CompiledClassHashMismatch,
StarknetError::UnsupportedTxVersion => StarknetRpcApiError::UnsupportedTxnVersion,
StarknetError::UnsupportedContractClassVersion => StarknetRpcApiError::UnsupportedContractClassVersion,
StarknetError::UnexpectedError(_) => StarknetRpcApiError::ErrUnexpectedError,
StarknetError::NoTraceAvailable(_) => StarknetRpcApiError::InternalServerError,
StarknetError::InvalidTransactionHash => StarknetRpcApiError::InvalidTxnHash,
}
}
}
113 changes: 38 additions & 75 deletions crates/client/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use starknet_core::types::{
MaybePendingTransactionReceipt, MsgFromL1, MsgToL1, StateDiff, StateUpdate, SyncStatus, SyncStatusType,
Transaction, TransactionExecutionStatus, TransactionFinalityStatus, TransactionReceipt,
};
use starknet_providers::{Provider, ProviderError, SequencerGatewayProvider};

use crate::constants::{MAX_EVENTS_CHUNK_SIZE, MAX_EVENTS_KEYS};
use crate::types::RpcEventFilter;
Expand Down Expand Up @@ -293,54 +294,21 @@ where
&self,
declare_transaction: BroadcastedDeclareTransaction,
) -> RpcResult<DeclareTransactionResult> {
let best_block_hash = self.client.info().best_hash;

let opt_sierra_contract_class = if let BroadcastedDeclareTransaction::V2(ref tx) = declare_transaction {
Some(flattened_sierra_to_sierra_contract_class(tx.contract_class.clone()))
} else {
None
};

let transaction: UserTransaction = declare_transaction.try_into().map_err(|e| {
error!("Failed to convert BroadcastedDeclareTransaction to UserTransaction, error: {e}");
StarknetRpcApiError::InternalServerError
})?;
let class_hash = match transaction {
UserTransaction::Declare(ref tx, _) => tx.class_hash(),
_ => Err(StarknetRpcApiError::InternalServerError)?,
};

let current_block_hash = self.client.info().best_hash;
let contract_class = self
.overrides
.for_block_hash(self.client.as_ref(), current_block_hash)
.contract_class_by_class_hash(current_block_hash, (*class_hash).into());
let config = get_config();
let sequencer = SequencerGatewayProvider::new(config.feeder_gateway, config.gateway, config.chain_id);

if let Some(contract_class) = contract_class {
error!("Contract class already exists: {:?}", contract_class);
return Err(StarknetRpcApiError::ClassAlreadyDeclared.into());
}

let extrinsic = convert_tx_to_extrinsic(self.client.clone(), best_block_hash, transaction.clone()).await?;

submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?;

let chain_id = Felt252Wrapper(self.chain_id()?.0);

let tx_hash = transaction.compute_hash::<H>(chain_id, false, None).into();

if let Some(sierra_contract_class) = opt_sierra_contract_class {
if let Some(e) = self
.backend
.sierra_classes()
.store_sierra_class(Felt252Wrapper::from(class_hash.0).into(), sierra_contract_class)
.err()
{
log::error!("Failed to store the sierra contract class for declare tx `{tx_hash:x}`: {e}")
let sequencer_response = match sequencer.add_declare_transaction(declare_transaction).await {
Ok(response) => response,
Err(ProviderError::StarknetError(e)) => {
return Err(StarknetRpcApiError::from(e).into());
}
}
Err(e) => {
error!("Failed to add invoke transaction to sequencer: {e}");
return Err(StarknetRpcApiError::InternalServerError.into());
}
};

Ok(DeclareTransactionResult { transaction_hash: tx_hash, class_hash: class_hash.0 })
Ok(sequencer_response)
}

/// Add an Invoke Transaction to invoke a contract function
Expand All @@ -356,20 +324,21 @@ where
&self,
invoke_transaction: BroadcastedInvokeTransaction,
) -> RpcResult<InvokeTransactionResult> {
let best_block_hash = self.client.info().best_hash;

let transaction: UserTransaction = invoke_transaction.try_into().map_err(|e| {
error!("Failed to convert BroadcastedInvokeTransaction to UserTransaction: {e}");
StarknetRpcApiError::InternalServerError
})?;

let extrinsic = convert_tx_to_extrinsic(self.client.clone(), best_block_hash, transaction.clone()).await?;

submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?;
let config = get_config();
let sequencer = SequencerGatewayProvider::new(config.feeder_gateway, config.gateway, config.chain_id);

let chain_id = Felt252Wrapper(self.chain_id()?.0);
let sequencer_response = match sequencer.add_invoke_transaction(invoke_transaction).await {
Ok(response) => response,
Err(ProviderError::StarknetError(e)) => {
return Err(StarknetRpcApiError::from(e).into());
}
Err(e) => {
error!("Failed to add invoke transaction to sequencer: {e}");
return Err(StarknetRpcApiError::InternalServerError.into());
}
};

Ok(InvokeTransactionResult { transaction_hash: transaction.compute_hash::<H>(chain_id, false, None).into() })
Ok(sequencer_response)
}

/// Add an Deploy Account Transaction
Expand All @@ -386,27 +355,21 @@ where
&self,
deploy_account_transaction: BroadcastedDeployAccountTransaction,
) -> RpcResult<DeployAccountTransactionResult> {
let best_block_hash = self.client.info().best_hash;

let transaction: UserTransaction = deploy_account_transaction.try_into().map_err(|e| {
error!("Failed to convert BroadcastedDeployAccountTransaction to UserTransaction, error: {e}",);
StarknetRpcApiError::InternalServerError
})?;

let extrinsic = convert_tx_to_extrinsic(self.client.clone(), best_block_hash, transaction.clone()).await?;

submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?;
let config = get_config();
let sequencer = SequencerGatewayProvider::new(config.feeder_gateway, config.gateway, config.chain_id);

let chain_id = Felt252Wrapper(self.chain_id()?.0);
let account_address = match &transaction {
UserTransaction::DeployAccount(tx) => tx.account_address(),
_ => Err(StarknetRpcApiError::InternalServerError)?,
let sequencer_response = match sequencer.add_deploy_account_transaction(deploy_account_transaction).await {
Ok(response) => response,
Err(ProviderError::StarknetError(e)) => {
return Err(StarknetRpcApiError::from(e).into());
}
Err(e) => {
error!("Failed to add invoke transaction to sequencer: {e}");
return Err(StarknetRpcApiError::InternalServerError.into());
}
};

Ok(DeployAccountTransactionResult {
transaction_hash: transaction.compute_hash::<H>(chain_id, false, None).into(),
contract_address: account_address.into(),
})
Ok(sequencer_response)
}
}

Expand Down

0 comments on commit fbfb06e

Please sign in to comment.