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

add starknet transaction hash to transaction_by_hash #1524

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ test-ci: load-env
test-target: load-env
cargo test --tests --all-features $(TARGET) -- --nocapture

test-target1: load-env
cargo test --package kakarot-rpc --test entry --all-features -- tests::eth_provider::test_transaction_by_hash --exact --show-output

tcoratger marked this conversation as resolved.
Show resolved Hide resolved
benchmark:
cd benchmarks && bun i && bun run benchmark

Expand Down
1 change: 1 addition & 0 deletions src/bin/hive_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ async fn main() -> eyre::Result<()> {
args.relayer_address,
relayer_balance,
JsonRpcClient::new(HttpTransport::new(Url::from_str(STARKNET_RPC_URL)?)),
None,
);

// Read the rlp file
Expand Down
34 changes: 31 additions & 3 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ use crate::{
},
providers::{
eth_provider::{
database::{types::transaction::ExtendedTransaction, Database},
database::{
filter,
filter::EthDatabaseFilterBuilder,
types::transaction::{ExtendedTransaction, StoredEthStarknetTransactionHash},
Database,
},
error::SignatureError,
provider::{EthApiResult, EthDataProvider},
TransactionProvider, TxPoolProvider,
Expand Down Expand Up @@ -170,13 +175,36 @@ where
SP: starknet::providers::Provider + Send + Sync,
{
async fn transaction_by_hash(&self, hash: B256) -> EthApiResult<Option<ExtendedTransaction>> {
Ok(self
// Try to get the information from:
// 1. The pool if the transaction is in the pool.
// 2. The Ethereum provider if the transaction is not in the pool.
let mut tx = self
.pool
.get(&hash)
.map(|transaction| {
TransactionSource::Pool(transaction.transaction.transaction().clone())
.into_transaction::<reth_rpc::eth::EthTxBuilder>()
})
.or(self.eth_provider.transaction_by_hash(hash).await?))
.or(self.eth_provider.transaction_by_hash(hash).await?);

if let Some(ref mut transaction) = tx {
// Fetch the Starknet transaction hash if it exists.
let filter = EthDatabaseFilterBuilder::<filter::EthStarknetTransactionHash>::default()
.with_tx_hash(&transaction.hash)
.build();

let hash_mapping: Option<StoredEthStarknetTransactionHash> =
self.eth_provider.database().get_one(filter, None).await?;

// Add the Starknet transaction hash to the transaction fields.
if let Some(hash_mapping) = hash_mapping {
transaction.other.insert(
"starknet_transaction_hash".to_string(),
serde_json::Value::String(hash_mapping.hashes.starknet_hash.to_fixed_hex_string()),
);
}
}

Ok(tx)
}
}
1 change: 1 addition & 0 deletions src/pool/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ impl<SP: starknet::providers::Provider + Send + Sync + Clone + 'static> AccountM
account_address,
balance,
JsonRpcClient::new(HttpTransport::new(KAKAROT_RPC_CONFIG.network_url.clone())),
Some(Arc::new(self.eth_client.eth_provider().database().clone())),
);

// Return the locked relayer instance
Expand Down
71 changes: 70 additions & 1 deletion src/providers/eth_provider/database/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use super::{
},
Database,
};
use crate::providers::eth_provider::error::EthApiError;
use crate::providers::eth_provider::{
database::types::transaction::{EthStarknetHashes, StoredEthStarknetTransactionHash},
error::EthApiError,
};
use alloy_primitives::{B256, U256};
use alloy_rlp::Encodable;
use alloy_rpc_types::{Block, BlockHashOrNumber, BlockTransactions, Header};
Expand All @@ -31,6 +34,8 @@ pub trait EthereumTransactionStore {
) -> Result<Vec<ExtendedTransaction>, EthApiError>;
/// Upserts the given transaction.
async fn upsert_transaction(&self, transaction: ExtendedTransaction) -> Result<(), EthApiError>;
/// Upserts the given transaction hash mapping (Ethereum -> Starknet).
async fn upsert_transaction_hashes(&self, transaction_hashes: EthStarknetHashes) -> Result<(), EthApiError>;
}

#[async_trait]
Expand Down Expand Up @@ -58,6 +63,14 @@ impl EthereumTransactionStore for Database {
let filter = EthDatabaseFilterBuilder::<filter::Transaction>::default().with_tx_hash(&transaction.hash).build();
Ok(self.update_one(StoredTransaction::from(transaction), filter, true).await?)
}

#[instrument(skip_all, name = "db::upsert_transaction_hashes", err)]
async fn upsert_transaction_hashes(&self, transaction_hashes: EthStarknetHashes) -> Result<(), EthApiError> {
let filter = EthDatabaseFilterBuilder::<filter::EthStarknetTransactionHash>::default()
.with_tx_hash(&transaction_hashes.eth_hash)
.build();
Ok(self.update_one(StoredEthStarknetTransactionHash::from(transaction_hashes), filter, true).await?)
}
}

/// Trait for interacting with a database that stores Ethereum typed
Expand Down Expand Up @@ -177,6 +190,7 @@ mod tests {
use crate::test_utils::mongo::{MongoFuzzer, RANDOM_BYTES_SIZE};
use arbitrary::Arbitrary;
use rand::{self, Rng};
use starknet::core::types::Felt;

#[tokio::test(flavor = "multi_thread")]
async fn test_ethereum_transaction_store() {
Expand Down Expand Up @@ -392,4 +406,59 @@ mod tests {
// Test retrieving non-existing transaction count by block number
assert_eq!(database.transaction_count(rng.gen::<u64>().into()).await.unwrap(), None);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_upsert_transaction_hashes() {
// Initialize MongoDB fuzzer
let mut mongo_fuzzer = MongoFuzzer::new(RANDOM_BYTES_SIZE).await;

// Mock a database with sample data
let database = mongo_fuzzer.mock_database(1).await;

// Generate random Ethereum and Starknet hashes
let eth_hash = B256::random();
let starknet_hash =
Felt::from_hex("0x03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb").unwrap();

// Define an EthStarknetHashes instance for testing
let transaction_hashes = EthStarknetHashes { eth_hash, starknet_hash };

// First, upsert the transaction hash mapping (should insert as it doesn't exist initially)
database
.upsert_transaction_hashes(transaction_hashes.clone())
.await
.expect("Failed to upsert transaction hash mapping");

// Retrieve the inserted transaction hash mapping and verify it matches the inserted values
let filter =
EthDatabaseFilterBuilder::<filter::EthStarknetTransactionHash>::default().with_tx_hash(&eth_hash).build();
let stored_mapping: Option<StoredEthStarknetTransactionHash> =
database.get_one(filter.clone(), None).await.expect("Failed to retrieve transaction hash mapping");

assert_eq!(
stored_mapping,
Some(StoredEthStarknetTransactionHash::from(transaction_hashes.clone())),
"The transaction hash mapping was not inserted correctly"
);

// Now, modify the Starknet hash and upsert the modified transaction hash mapping
let new_starknet_hash =
Felt::from_hex("0x0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a").unwrap();
let updated_transaction_hashes = EthStarknetHashes { eth_hash, starknet_hash: new_starknet_hash };

database
.upsert_transaction_hashes(updated_transaction_hashes.clone())
.await
.expect("Failed to update transaction hash mapping");

// Retrieve the updated transaction hash mapping and verify it matches the updated values
let updated_mapping: Option<StoredEthStarknetTransactionHash> =
database.get_one(filter, None).await.expect("Failed to retrieve updated transaction hash mapping");

assert_eq!(
updated_mapping,
Some(StoredEthStarknetTransactionHash::from(updated_transaction_hashes)),
"The transaction hash mapping was not updated correctly"
);
}
}
22 changes: 22 additions & 0 deletions src/providers/eth_provider/database/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ pub trait LogFiltering {
fn address(&self) -> &'static str;
}

/// A type used for a mapping between:
/// - An Ethereum transaction hash
/// - A Starknet transaction hash.
#[derive(Debug, Default)]
pub struct EthStarknetTransactionHash;

impl Display for EthStarknetTransactionHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "hashes")
}
}

impl TransactionFiltering for EthStarknetTransactionHash {
fn transaction_hash(&self) -> &'static str {
"eth_hash"
}

fn transaction_index(&self) -> &'static str {
""
}
}

/// A transaction type used as a target for the filter.
#[derive(Debug, Default)]
pub struct Transaction;
Expand Down
12 changes: 11 additions & 1 deletion src/providers/eth_provider/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ pub mod types;

use super::error::KakarotError;
use crate::providers::eth_provider::database::types::{
header::StoredHeader, log::StoredLog, receipt::StoredTransactionReceipt, transaction::StoredTransaction,
header::StoredHeader,
log::StoredLog,
receipt::StoredTransactionReceipt,
transaction::{StoredEthStarknetTransactionHash, StoredTransaction},
};
use futures::TryStreamExt;
use itertools::Itertools;
Expand Down Expand Up @@ -235,3 +238,10 @@ impl CollectionName for StoredLog {
"logs"
}
}

/// Implement [`CollectionName`] for [`StoredEthStarknetTransactionHash`]
impl CollectionName for StoredEthStarknetTransactionHash {
fn collection_name() -> &'static str {
"transaction_hashes"
}
}
25 changes: 25 additions & 0 deletions src/providers/eth_provider/database/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use alloy_primitives::B256;
use alloy_rpc_types::Transaction;
use alloy_serde::WithOtherFields;
use serde::{Deserialize, Serialize};
use starknet::core::types::Felt;
use std::ops::Deref;
#[cfg(any(test, feature = "arbitrary", feature = "testing"))]
use {
Expand All @@ -11,9 +12,33 @@ use {
reth_primitives::transaction::legacy_parity,
reth_testing_utils::generators::{self},
};

/// Type alias for a transaction with additional fields.
pub type ExtendedTransaction = WithOtherFields<Transaction>;

/// A mapping between an Ethereum transaction hash and a Starknet transaction hash.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct StoredEthStarknetTransactionHash {
/// Contains both Ethereum and Starknet transaction hashes.
#[serde(deserialize_with = "crate::providers::eth_provider::database::types::serde::deserialize_intermediate")]
pub hashes: EthStarknetHashes,
}

impl From<EthStarknetHashes> for StoredEthStarknetTransactionHash {
fn from(hashes: EthStarknetHashes) -> Self {
Self { hashes }
}
}

/// Inner struct that holds the Ethereum and Starknet transaction hashes.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct EthStarknetHashes {
/// The Ethereum transaction hash.
pub eth_hash: B256,
/// The Starknet transaction hash.
pub starknet_hash: Felt,
}

/// A full transaction as stored in the database
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct StoredTransaction {
Expand Down
25 changes: 22 additions & 3 deletions src/providers/eth_provider/starknet/relayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
constants::STARKNET_CHAIN_ID,
models::transaction::transaction_data_to_starknet_calldata,
providers::eth_provider::{
database::{ethereum::EthereumTransactionStore, types::transaction::EthStarknetHashes, Database},
error::{SignatureError, TransactionError},
provider::EthApiResult,
starknet::kakarot_core::{starknet_address, EXECUTE_FROM_OUTSIDE},
Expand All @@ -14,7 +15,12 @@ use starknet::{
providers::Provider,
signers::{LocalWallet, SigningKey},
};
use std::{env::var, ops::Deref, str::FromStr, sync::LazyLock};
use std::{
env::var,
ops::Deref,
str::FromStr,
sync::{Arc, LazyLock},
};

/// Signer for all relayers
static RELAYER_SIGNER: LazyLock<LocalWallet> = LazyLock::new(|| {
Expand All @@ -33,14 +39,16 @@ pub struct Relayer<SP: Provider + Send + Sync> {
account: SingleOwnerAccount<SP, LocalWallet>,
/// The balance of the relayer
balance: Felt,
/// The database used to store the relayer's transaction hashes map (Ethereum -> Starknet)
database: Option<Arc<Database>>,
}

impl<SP> Relayer<SP>
where
SP: Provider + Send + Sync,
{
/// Create a new relayer with the provided Starknet provider, address, balance.
pub fn new(address: Felt, balance: Felt, provider: SP) -> Self {
pub fn new(address: Felt, balance: Felt, provider: SP, database: Option<Arc<Database>>) -> Self {
let relayer = SingleOwnerAccount::new(
provider,
RELAYER_SIGNER.clone(),
Expand All @@ -49,7 +57,7 @@ where
ExecutionEncoding::New,
);

Self { account: relayer, balance }
Self { account: relayer, balance, database }
}

/// Relay the provided Ethereum transaction on the Starknet network.
Expand Down Expand Up @@ -87,6 +95,17 @@ where
let prepared = execution.prepared().map_err(|_| SignatureError::SigningFailure)?;
let res = prepared.send().await.map_err(|err| TransactionError::Broadcast(err.into()))?;

// Store a transaction hash mapping from Ethereum to Starknet in the database

if let Some(database) = &self.database {
database
.upsert_transaction_hashes(EthStarknetHashes {
eth_hash: transaction.hash,
starknet_hash: res.transaction_hash,
})
.await?;
}

Ok(res.transaction_hash)
}

Expand Down
26 changes: 18 additions & 8 deletions src/test_utils/eoa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,15 @@ impl<P: Provider + Send + Sync + Clone> KakarotEOA<P> {
let relayer_balance = into_via_try_wrapper!(relayer_balance)?;

// Relay the transaction
let starknet_transaction_hash = Relayer::new(self.relayer.address(), relayer_balance, self.starknet_provider())
.relay_transaction(&tx_signed)
.await
.expect("Failed to relay transaction");
let starknet_transaction_hash = Relayer::new(
self.relayer.address(),
relayer_balance,
self.starknet_provider(),
Some(Arc::new(self.eth_client.eth_provider().database().clone())),
)
.relay_transaction(&tx_signed)
.await
.expect("Failed to relay transaction");

watch_tx(
self.eth_client.eth_provider().starknet_provider_inner(),
Expand Down Expand Up @@ -226,10 +231,15 @@ impl<P: Provider + Send + Sync + Clone> KakarotEOA<P> {
let relayer_balance = into_via_try_wrapper!(relayer_balance)?;

// Relay the transaction
let starknet_transaction_hash = Relayer::new(self.relayer.address(), relayer_balance, self.starknet_provider())
.relay_transaction(&tx_signed)
.await
.expect("Failed to relay transaction");
let starknet_transaction_hash = Relayer::new(
self.relayer.address(),
relayer_balance,
self.starknet_provider(),
Some(Arc::new(self.eth_client.eth_provider().database().clone())),
)
.relay_transaction(&tx_signed)
.await
.expect("Failed to relay transaction");

watch_tx(
self.eth_client.eth_provider().starknet_provider_inner(),
Expand Down
Loading
Loading