diff --git a/.gitignore b/.gitignore index 27621ba..3b0f5fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /Node/target .vscode +tools/tx_spammer/.env +tools/tx_spammer/venv \ No newline at end of file diff --git a/Node/Cargo.lock b/Node/Cargo.lock index 8774cdc..2f1e58d 100644 --- a/Node/Cargo.lock +++ b/Node/Cargo.lock @@ -832,15 +832,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] - [[package]] name = "bimap" version = "0.6.3" @@ -1732,9 +1723,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a130d27083a4001b7b2d72a19f08786299550f76c9bd5307498dce2c2b20fa" +checksum = "e419d6c39cb9632288c592a06d7d0a96740021b0bff812e211ace754b0fe8c9a" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -1745,13 +1736,11 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21545a9445fbd582840ff5160a9a3e12b8e6da582151cdb07bde9a1970ba3a24" +checksum = "d06b8be79a3bdd7d87c1d95c0e939052b7f64fffce7b9436986e43e92f20a978" dependencies = [ - "anyhow", "async-trait", - "beef", "bytes", "futures-util", "http", @@ -1770,9 +1759,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb25cab482c8512c4f3323a5c90b95a3b8f7c90681a87bf7a68b942d52f08933" +checksum = "52dc99c70619e252e6adc5e95144323505a69a1742771de5b3f2071e1595b363" dependencies = [ "async-trait", "base64 0.22.1", @@ -1795,11 +1784,10 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810f63eff0f78fa8d413d678c0e55b702e2ea61d4587774c0db4ea2fc554ef92" +checksum = "8c358788aa585f51a78b11bec5d4b16fbe26dda1cc149f21d95dc24836a0be83" dependencies = [ - "anyhow", "futures-util", "http", "http-body", @@ -1823,11 +1811,10 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f511b714bca46f9a3e97c0e0eb21d2c112e83e444d2db535b5ec7093f5836d73" +checksum = "0feba38a9878d70ccccd2f54b534b15e861d6caa7911d59abfd3e0d8b4de091f" dependencies = [ - "beef", "http", "serde", "serde_json", @@ -2539,9 +2526,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc-hex" @@ -2724,6 +2711,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.0" @@ -3057,8 +3062,10 @@ dependencies = [ "k256", "lazy_static", "reqwest", + "secp256k1", "serde", "serde_json", + "tiny-keccak", "tokio", "tracing", "tracing-subscriber", diff --git a/Node/Cargo.toml b/Node/Cargo.toml index a3b74a0..57b03b5 100644 --- a/Node/Cargo.toml +++ b/Node/Cargo.toml @@ -6,14 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -alloy = { version = "0.1.2", features = [ - "full", - "node-bindings", -] } +alloy = { version = "0.1.2", features = ["full", "node-bindings"] } tokio = { version = "1.38", features = ["full"] } tracing = "0.1.40" tracing-subscriber = "0.3" -jsonrpsee = { version = "0.23", features = ["http-client", "server"] } +jsonrpsee = { version = "0.24", features = ["http-client", "server"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" lazy_static = "1.4" @@ -22,3 +19,5 @@ k256 = "0.13" elliptic-curve = "0.13" reqwest = "0.12" hex = "0.4" +tiny-keccak = "2.0" +secp256k1 = "0.29" diff --git a/Node/src/utils/commit.rs b/Node/src/utils/commit.rs new file mode 100644 index 0000000..a68715d --- /dev/null +++ b/Node/src/utils/commit.rs @@ -0,0 +1,81 @@ +use crate::taiko::l2_tx_lists::RPCReplyL2TxLists; +use anyhow::Error; +use secp256k1::{ecdsa::Signature, Message, Secp256k1, SecretKey}; +use serde::{Deserialize, Serialize}; +use tiny_keccak::{Hasher, Keccak}; + +#[derive(Serialize, Deserialize)] +pub struct L2TxListsCommit { + pub tx_list_bytes: Vec, + pub parent_meta_hash: [u8; 32], + pub block_height: u64, +} + +impl From for L2TxListsCommit { + fn from(reply: RPCReplyL2TxLists) -> Self { + L2TxListsCommit { + tx_list_bytes: reply.tx_list_bytes[0].clone(), // TODO check for other indexes + parent_meta_hash: reply.parent_meta_hash, + block_height: 1, //TODO add to the replay + } + } +} + +impl L2TxListsCommit { + pub fn hash(&self) -> Result<[u8; 32], Error> { + let serialized = serde_json::to_vec(&self)?; + let mut hasher = Keccak::v256(); + hasher.update(&serialized); + let mut result = [0u8; 32]; + hasher.finalize(&mut result); + Ok(result) + } + + pub fn sign(&self, private_key: &str) -> Result { + let secp = Secp256k1::new(); + let secret_key = SecretKey::from_slice(&hex::decode(private_key)?)?; + let message = Message::from_digest_slice(&self.hash()?)?; + let signature = secp.sign_ecdsa(&message, &secret_key); + Ok(signature) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash() { + let commit = L2TxListsCommit { + tx_list_bytes: vec![1, 2, 3, 4, 5], + parent_meta_hash: [0u8; 32], + block_height: 1, + }; + + let hash_result = commit.hash(); + assert!(hash_result.is_ok()); + let hash = hash_result.unwrap(); + assert_eq!(hash.len(), 32); + } + + #[test] + fn test_sign() { + let commit = L2TxListsCommit { + tx_list_bytes: vec![1, 2, 3, 4, 5], + parent_meta_hash: [0u8; 32], + block_height: 1, + }; + + let private_key = "c87509a1c067bbde78beb793e6fa950b8d9c7f7bd5a8b16bf0d3a1a5b9bdfd3b"; + let sign_result = commit.sign(private_key); + assert!(sign_result.is_ok()); + + let signature = sign_result.unwrap(); + let secp = Secp256k1::new(); + let public_key = SecretKey::from_slice(&hex::decode(private_key).unwrap()) + .unwrap() + .public_key(&secp); + let message = Message::from_digest_slice(&commit.hash().unwrap()).unwrap(); + assert!(secp.verify_ecdsa(&message, &signature, &public_key).is_ok()); + } +} diff --git a/Node/src/utils/mod.rs b/Node/src/utils/mod.rs index 29186bd..c983a1e 100644 --- a/Node/src/utils/mod.rs +++ b/Node/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod commit; pub mod config; pub mod rpc_client; pub mod rpc_server; diff --git a/tools/tx_spammer/requirements.txt b/tools/tx_spammer/requirements.txt new file mode 100644 index 0000000..7a7c7b3 --- /dev/null +++ b/tools/tx_spammer/requirements.txt @@ -0,0 +1,2 @@ +web3 +python-dotenv \ No newline at end of file diff --git a/tools/tx_spammer/tx_spammer.py b/tools/tx_spammer/tx_spammer.py new file mode 100644 index 0000000..6f4b4e7 --- /dev/null +++ b/tools/tx_spammer/tx_spammer.py @@ -0,0 +1,84 @@ +r""" +This script is used to spam transactions on the Taiko network. It reads the private key and recipient address from a .env file, +connects to the Taiko network, and sends a specified number of transactions to the recipient address. + +Setup: +1. Create a virtual environment: + python -m venv venv + +2. Activate the virtual environment: + - On Windows: venv\Scripts\activate + - On macOS/Linux: source venv/bin/activate + +3. Install the required dependencies: + pip install -r requirements.txt + +4. Create a .env file in the tools/tx_spammer directory with the following content: + PRIVATE_KEY= + RECIPIENT_ADDRESS= + +5. Run the script: + python tx_spammer.py [--count COUNT] [--amount AMOUNT] + +CLI Parameters: +--count: Number of transactions to send (default: 1) +--amount: Amount of ETH to send per transaction (default: 0.006) +""" + + + +import time +from web3 import Web3 +import os +from dotenv import load_dotenv +import argparse + +# Load environment variables from .env file +load_dotenv() + +private_key = os.getenv('PRIVATE_KEY') +if not private_key: + raise Exception("Environment variable PRIVATE_KEY not set") + +recipient = os.getenv('RECIPIENT_ADDRESS') +if not recipient: + raise Exception("Environment variable RECIPIENT_ADDRESS not set") + +parser = argparse.ArgumentParser(description='Spam transactions on the Taiko network.') +parser.add_argument('--count', type=int, default=1, help='Number of transactions to send') +parser.add_argument('--amount', type=float, default=0.006, help='Amount of ETH to send per transaction') +args = parser.parse_args() + +# Connect to the Taiko network +w3 = Web3(Web3.HTTPProvider('https://RPC.helder.taiko.xyz')) + +# Check if connected +if not w3.is_connected(): + raise Exception("Failed to connect to the Taiko network") + +# Get the account from the private key +account = w3.eth.account.from_key(private_key) +amount = w3.to_wei(args.amount, 'ether') + +def send_transaction(nonce : int): + tx = { + 'nonce': nonce, + 'to': recipient, + 'value': amount, + 'gas': 21000, + 'gasPrice': w3.to_wei('10', 'gwei'), + 'chainId': w3.eth.chain_id + } + print(f'Sending transaction: {tx}') + signed_tx = w3.eth.account.sign_transaction(tx, private_key) + tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) + print(f'Transaction sent: {tx_hash.hex()}') + +def spam_transactions(count): + nonce = w3.eth.get_transaction_count(account.address) + for _ in range(count): + send_transaction(nonce) + nonce += 1 + time.sleep(0.01) # Add a delay to avoid nonce issues + +spam_transactions(args.count) \ No newline at end of file