diff --git a/Cargo.lock b/Cargo.lock index 4a39c2c6e..849eee1e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,7 @@ checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -69,7 +69,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "syn-solidity", "tiny-keccak", ] @@ -87,9 +87,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "async-trait" @@ -99,7 +99,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.85" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b918671670962b48bc23753aef0c51d072dca6f52f01f800854ada6ddb7f7d3" +checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" [[package]] name = "cfg-if" @@ -251,9 +251,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" [[package]] name = "hex" @@ -305,6 +305,7 @@ dependencies = [ "anyhow", "async-trait", "serde", + "unsigned-varint", ] [[package]] @@ -628,7 +629,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -648,12 +649,12 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -678,9 +679,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -696,7 +697,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -747,7 +748,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -762,6 +763,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "valuable" version = "0.1.0" @@ -795,7 +802,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -815,17 +822,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -836,9 +843,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -848,9 +855,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -860,9 +867,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -872,9 +879,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -884,9 +891,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -896,9 +903,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -908,9 +915,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 97a256d83..e78f9755e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ exclude = ["**/target", "benches/", "tests"] [workspace.dependencies] anyhow = { version = "1.0.79", default-features = false } -tracing = "0.1.40" cfg-if = "1.0.0" [profile.dev] diff --git a/crates/derive/Cargo.toml b/crates/derive/Cargo.toml index d628856c7..5450e304c 100644 --- a/crates/derive/Cargo.toml +++ b/crates/derive/Cargo.toml @@ -17,6 +17,7 @@ alloy-primitives = { version = "0.6.3", default-features = false, features = ["r alloy-rlp = { version = "0.3.4", default-features = false, features = ["derive"] } alloy-sol-types = { version = "0.6.3", default-features = false } async-trait = "0.1.77" +unsigned-varint = "0.8.0" # Optional serde = { version = "1.0.197", default-features = false, features = ["derive"], optional = true } diff --git a/crates/derive/src/lib.rs b/crates/derive/src/lib.rs index 79206508f..122cf4f42 100644 --- a/crates/derive/src/lib.rs +++ b/crates/derive/src/lib.rs @@ -12,6 +12,7 @@ #![allow(dead_code, unused, unreachable_pub)] extern crate alloc; +extern crate std; pub mod stages; pub mod traits; diff --git a/crates/derive/src/types/mod.rs b/crates/derive/src/types/mod.rs index 52fc6be11..285b6087a 100644 --- a/crates/derive/src/types/mod.rs +++ b/crates/derive/src/types/mod.rs @@ -29,3 +29,102 @@ pub use eips::{ mod genesis; pub use genesis::Genesis; + +use alloc::string::String; +use alloc::vec::Vec; +use alloy_primitives::{hex, Address, BlockHash}; +use alloy_rlp::Decodable; + +mod single_batch; +pub use single_batch::SingleBatch; + +mod span_batch; +pub use span_batch::SpanBatch; + +/// A raw transaction +#[derive(Clone, PartialEq, Eq)] +pub struct RawTransaction(pub Vec); + +impl Decodable for RawTransaction { + /// Decodes RLP encoded bytes into [RawTransaction] bytes + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let tx_bytes: Vec = Decodable::decode(buf)?; + Ok(Self(tx_bytes)) + } +} + +/// A single L2 block derived from a batch. +#[derive(Clone)] +pub struct BlockInput { + /// Timestamp of the L2 block + pub timestamp: u64, + /// Transactions included in this block + pub transactions: Vec, + /// The L1 block this batch was fully derived from + pub l1_inclusion_block: u64, +} + +/// The global `Magi` configuration. +#[derive(Clone)] +pub struct Config { + /// The L1 chain RPC URL + pub l1_rpc_url: String, + /// The L2 chain RPC URL + pub l2_rpc_url: String, + /// The L2 engine API URL + pub l2_engine_url: String, + /// The L2 chain config + pub chain: ChainConfig, + /// Engine API JWT Secret. + /// This is used to authenticate with the engine API + pub jwt_secret: String, + /// A trusted L2 RPC URL to use for fast/checkpoint syncing + pub checkpoint_sync_url: Option, + /// The port of the `Magi` RPC server + pub rpc_port: u16, + /// If devnet is enabled. + pub devnet: bool, +} + +/// Configurations for a blockchain. +#[derive(Clone)] +pub struct ChainConfig { + /// The network name + pub network: String, + /// The L1 chain id + pub l1_chain_id: u64, + /// The L2 chain id + pub l2_chain_id: u64, + /* + /// The L1 genesis block referenced by the L2 chain + pub l1_start_epoch: Epoch, + */ + /// The L2 genesis block info + pub l2_genesis: BlockInfo, + /* + /// The initial system config value + pub system_config: SystemConfig, + */ + /// The batch inbox address + pub batch_inbox: Address, + /// The deposit contract address + pub deposit_contract: Address, + /// The L1 system config contract address + pub system_config_contract: Address, + /// The maximum byte size of all pending channels + pub max_channel_size: u64, + /// The max timeout for a channel (as measured by the frame L1 block number) + pub channel_timeout: u64, + /// Number of L1 blocks in a sequence window + pub seq_window_size: u64, + /// Maximum timestamp drift + pub max_seq_drift: u64, + /// Timestamp of the regolith hardfork + pub regolith_time: u64, + /// Timestamp of the canyon hardfork + pub canyon_time: u64, + /// Timestamp of the delta hardfork + pub delta_time: u64, + /// Network blocktime + pub blocktime: u64, +} diff --git a/crates/derive/src/types/single_batch.rs b/crates/derive/src/types/single_batch.rs new file mode 100644 index 000000000..b3bb7dfdc --- /dev/null +++ b/crates/derive/src/types/single_batch.rs @@ -0,0 +1,45 @@ +use super::RawTransaction; +use alloc::vec::Vec; +use alloy_primitives::BlockHash; +use alloy_rlp::Decodable; + +/// Represents a single batch: a single encoded L2 block +#[derive(Clone)] +pub struct SingleBatch { + /// Block hash of the previous L2 block + pub parent_hash: BlockHash, + /// The batch epoch number. Same as the first L1 block number in the epoch. + pub epoch_num: u64, + /// The block hash of the first L1 block in the epoch + pub epoch_hash: BlockHash, + /// The L2 block timestamp of this batch + pub timestamp: u64, + /// The L2 block transactions in this batch + pub transactions: Vec, +} + +impl SingleBatch { + /// Decodes RLP bytes into a [SingleBatch] + pub fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let parent_hash = Decodable::decode(buf)?; + let epoch_num = Decodable::decode(buf)?; + let epoch_hash = Decodable::decode(buf)?; + let timestamp = Decodable::decode(buf)?; + let transactions = Decodable::decode(buf)?; + + Ok(SingleBatch { + parent_hash, + epoch_num, + epoch_hash, + timestamp, + transactions, + }) + } + + /// If any transactions are empty or deposited transaction types. + pub fn has_invalid_transactions(&self) -> bool { + self.transactions + .iter() + .any(|tx| tx.0.is_empty() || tx.0[0] == 0x7E) + } +} diff --git a/crates/derive/src/types/span_batch.rs b/crates/derive/src/types/span_batch.rs new file mode 100644 index 000000000..6b05b3e50 --- /dev/null +++ b/crates/derive/src/types/span_batch.rs @@ -0,0 +1,509 @@ +use alloc::vec::Vec; +use alloy_primitives::{Address, Bytes, StorageKey, U256}; +use alloy_rlp::{BufMut, BytesMut, Decodable, Encodable}; + +use super::{BlockInput, Config, RawTransaction}; +use anyhow::Result; + +#[derive(Debug, Clone)] +pub struct AccessList(pub Vec); + +impl Encodable for AccessList { + fn length(&self) -> usize { + let mut len = 0; + for item in &self.0 { + len += item.address.length(); + len += item.storage_keys.len() * 32; + } + len + } + + fn encode(&self, out: &mut dyn BufMut) { + for item in &self.0 { + item.address.encode(out); + for key in &item.storage_keys { + key.encode(out); + } + } + } +} + +impl Decodable for AccessList { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let mut items = Vec::new(); + while !buf.is_empty() { + let address = Address::decode(buf)?; + let mut storage_keys = Vec::new(); + for _ in 0..buf.len() / 32 { + let key = StorageKey::decode(buf)?; + storage_keys.push(key); + } + items.push(AccessListItem { + address, + storage_keys, + }); + } + + Ok(Self(items)) + } +} + +#[derive(Debug, Clone)] +pub struct AccessListItem { + /// Account addresses that would be loaded at the start of execution + pub address: Address, + /// Keys of storage that would be loaded at the start of execution + pub storage_keys: Vec, +} + +/// Represents a span batch: a range of encoded L2 blocks +#[derive(Clone)] +pub struct SpanBatch { + /// Uvarint encoded relative timestamp since L2 genesis + pub rel_timestamp: u64, + /// Uvarint encoded L1 origin number of the last L2 block in the batch + pub l1_origin_num: u64, + /// First 20 bytes of the parent hash of the first L2 block in the batch. + pub parent_check: [u8; 20], + /// Last 20 bytes of the L1 origin hash of the last L2 block in the batch. + pub l1_origin_check: [u8; 20], + /// Uvarint encoded number of L2 blocks in the batch. + pub block_count: u64, + /// Bitlist of [SpanBatch.block_count] bits: 1 bit per block. + pub origin_bits: Vec, + /// Uvarint encoded number of L2 transactions in this batch + pub block_tx_counts: Vec, + /// The L2 transactions in this batch + pub transactions: Vec, + /// The L1 block number this batch was derived from. + pub l1_inclusion_block: u64, +} + +impl SpanBatch { + /// Decodes a sequence of bytes into a [SpanBatch] + pub fn decode(data: &[u8], l1_inclusion_block: u64, chain_id: u64) -> alloy_rlp::Result { + let (rel_timestamp, data) = unsigned_varint::decode::u64(data) + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode timestamp"))?; + let (l1_origin_num, data) = unsigned_varint::decode::u64(data) + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode l1 origin"))?; + + let (parent_check, data) = take_data(data, 20); + let (l1_origin_check, data) = take_data(data, 20); + let (block_count, data) = unsigned_varint::decode::u64(data) + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode block count"))?; + + let (origin_bits, data) = decode_bitlist(data, block_count); + let (block_tx_counts, data) = decode_block_tx_counts(data, block_count) + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode tx counts"))?; + + let total_txs = block_tx_counts.iter().sum(); + let (transactions, _) = decode_transactions(chain_id, data, total_txs) + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode transactions"))?; + + Ok(SpanBatch { + rel_timestamp, + l1_origin_num, + parent_check: parent_check + .try_into() + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode parent check"))?, + l1_origin_check: l1_origin_check + .try_into() + .map_err(|_| alloy_rlp::Error::Custom("Failed to decode l1 origin check"))?, + block_count, + block_tx_counts, + origin_bits, + transactions, + l1_inclusion_block, + }) + } + + /// Returns a [BlockInput] vector for this batch. Contains all L2 block in the batch. + pub fn block_inputs(&self, config: &Config) -> Vec { + let init_epoch_num = self.l1_origin_num + - self + .origin_bits + .iter() + .map(|b| if *b { 1 } else { 0 }) + .sum::(); + + let mut inputs = Vec::new(); + let mut epoch_num = init_epoch_num; + let mut tx_index = 0usize; + for i in 0..self.block_count as usize { + if self.origin_bits[i] { + epoch_num += 1; + } + + let tx_end = self.block_tx_counts[i] as usize; + let transactions = self.transactions[tx_index..tx_index + tx_end].to_vec(); + tx_index += self.block_tx_counts[i] as usize; + + let timestamp = self.rel_timestamp + + config.chain.l2_genesis.timestamp + + i as u64 * config.chain.blocktime; + + let block_input = BlockInput { + timestamp, + transactions, + l1_inclusion_block: self.l1_inclusion_block, + }; + + inputs.push(block_input); + } + + inputs + } + + /// Returns the L1 origin number of the last L2 block in the batch + pub fn start_epoch_num(&self) -> u64 { + self.l1_origin_num + - self + .origin_bits + .iter() + .map(|b| if *b { 1 } else { 0 }) + .sum::() + + if self.origin_bits[0] { 1 } else { 0 } + } +} + +/// Splits a byte slice at the specified index (length) into a tuple of 2 byte slices +fn take_data(data: &[u8], length: usize) -> (&[u8], &[u8]) { + (&data[0..length], &data[length..]) +} + +/// Decodes a bitlist into boolean values and returns a tuple of booleans + the original bitlist. +fn decode_bitlist(data: &[u8], len: u64) -> (Vec, &[u8]) { + let mut bitlist = Vec::new(); + + let len_up = (len + 7) / 8; + let (bytes, data) = take_data(data, len_up as usize); + + for byte in bytes.iter().rev() { + for i in 0..8 { + let bit = (byte >> i) & 1 == 1; + bitlist.push(bit); + } + } + + let bitlist = bitlist[..len as usize].to_vec(); + + (bitlist, data) +} + +/// Decodes the number of transactions in the batch into a U64 vector +fn decode_block_tx_counts(data: &[u8], block_count: u64) -> Result<(Vec, &[u8])> { + let mut tx_counts = Vec::new(); + let mut data_ref = data; + for _ in 0..block_count { + let (count, d) = unsigned_varint::decode::u64(data_ref).unwrap(); + data_ref = d; + tx_counts.push(count); + } + + Ok((tx_counts, data_ref)) +} + +/// Decodes transactions in a batch and returns a [RawTransaction] vector +fn decode_transactions( + chain_id: u64, + data: &[u8], + tx_count: u64, +) -> Result<(Vec, &[u8])> { + let (contract_creation_bits, data) = decode_bitlist(data, tx_count); + let (y_parity_bits, data) = decode_bitlist(data, tx_count); + let (signatures, data) = decode_signatures(data, tx_count); + + let tos_count = contract_creation_bits.iter().filter(|b| !**b).count() as u64; + let (tos, data) = decode_tos(data, tos_count); + + let (tx_datas, data) = decode_tx_data(data, tx_count); + let (tx_nonces, data) = decode_uvarint_list(data, tx_count); + let (tx_gas_limits, data) = decode_uvarint_list(data, tx_count); + + let legacy_tx_count = tx_datas + .iter() + .filter(|tx| matches!(tx, TxData::Legacy { .. })) + .count() as u64; + + let (protected_bits, data) = decode_bitlist(data, legacy_tx_count); + + let mut txs = Vec::new(); + let mut legacy_i = 0; + let mut tos_i = 0; + + for i in 0..tx_count as usize { + let mut encoder = BytesMut::new(); + + match &tx_datas[i] { + TxData::Legacy { + value, + gas_price, + data, + } => { + chain_id.encode(&mut encoder); + tx_nonces[i].encode(&mut encoder); + gas_price.encode(&mut encoder); + tx_gas_limits[i].encode(&mut encoder); + + if contract_creation_bits[i] { + "".encode(&mut encoder); + } else { + tos[tos_i].encode(&mut encoder); + tos_i += 1; + } + + value.encode(&mut encoder); + data.encode(&mut encoder); + + let parity = if y_parity_bits[i] { 1 } else { 0 }; + let v = if protected_bits[legacy_i] { + chain_id * 2 + 35 + parity + } else { + 27 + parity + }; + + v.encode(&mut encoder); + signatures[i].0.encode(&mut encoder); + signatures[i].1.encode(&mut encoder); + + let raw_tx = RawTransaction(encoder.to_vec()); + txs.push(raw_tx); + legacy_i += 1; + } + TxData::Type1 { + value, + gas_price, + data, + access_list, + } => { + chain_id.encode(&mut encoder); + tx_nonces[i].encode(&mut encoder); + gas_price.encode(&mut encoder); + tx_gas_limits[i].encode(&mut encoder); + + if contract_creation_bits[i] { + "".encode(&mut encoder); + } else { + tos[tos_i].encode(&mut encoder); + tos_i += 1; + } + + value.encode(&mut encoder); + data.encode(&mut encoder); + access_list.encode(&mut encoder); + + let parity = if y_parity_bits[i] { 1u64 } else { 0u64 }; + parity.encode(&mut encoder); + signatures[i].0.encode(&mut encoder); + signatures[i].1.encode(&mut encoder); + + let mut raw = encoder.to_vec(); + raw.insert(0, 1); + let raw_tx = RawTransaction(raw); + txs.push(raw_tx); + } + TxData::Type2 { + value, + max_fee, + max_priority_fee, + data, + access_list, + } => { + chain_id.encode(&mut encoder); + tx_nonces[i].encode(&mut encoder); + max_priority_fee.encode(&mut encoder); + max_fee.encode(&mut encoder); + tx_gas_limits[i].encode(&mut encoder); + + if contract_creation_bits[i] { + "".encode(&mut encoder); + } else { + tos[tos_i].encode(&mut encoder); + tos_i += 1; + } + + value.encode(&mut encoder); + data.encode(&mut encoder); + access_list.encode(&mut encoder); + + let parity = if y_parity_bits[i] { 1u64 } else { 0u64 }; + + parity.encode(&mut encoder); + signatures[i].0.encode(&mut encoder); + signatures[i].1.encode(&mut encoder); + + let mut raw = encoder.to_vec(); + raw.insert(0, 2); + + let raw_tx = RawTransaction(raw); + txs.push(raw_tx); + } + } + } + + Ok((txs, data)) +} + +/// Decodes transaction nonces in the batch into a U64 vector +fn decode_uvarint_list(data: &[u8], count: u64) -> (Vec, &[u8]) { + let mut list = Vec::new(); + let mut data_ref = data; + + for _ in 0..count { + let (nonce, d) = unsigned_varint::decode::u64(data_ref).unwrap(); + data_ref = d; + list.push(nonce); + } + + (list, data_ref) +} + +/// Decodes EIP-2718 `TransactionType` formatted transactions in the batch into a [TxData] vector +fn decode_tx_data(data: &[u8], tx_count: u64) -> (Vec, &[u8]) { + let mut data_ref = data; + let mut tx_datas = Vec::new(); + + for _ in 0..tx_count { + let (next, data) = match data_ref[0] { + 1 => { + let mut rlp = &data_ref[1..]; + + let value = U256::decode(&mut rlp).unwrap(); + let gas_price = U256::decode(&mut rlp).unwrap(); + let data = Vec::::decode(&mut rlp).unwrap(); + let access_list = AccessList::decode(&mut rlp).unwrap(); + + let next = rlp.len() + 1; + let data = TxData::Type1 { + value, + gas_price, + data: data.into(), + access_list, + }; + + (next, data) + } + 2 => { + let mut rlp = &data_ref[1..]; + let value = U256::decode(&mut rlp).unwrap(); + let max_priority_fee = U256::decode(&mut rlp).unwrap(); + let max_fee = U256::decode(&mut rlp).unwrap(); + let data = Vec::::decode(&mut rlp).unwrap(); + let access_list = AccessList::decode(&mut rlp).unwrap(); + + let next = rlp.len() + 1; + let data = TxData::Type2 { + value, + max_fee, + max_priority_fee, + data: data.into(), + access_list, + }; + + (next, data) + } + _ => { + let mut rlp = &data_ref[1..]; + let value = U256::decode(&mut rlp).unwrap(); + let gas_price = U256::decode(&mut rlp).unwrap(); + let data = Vec::::decode(&mut rlp).unwrap(); + + let next = rlp.len(); + let data = TxData::Legacy { + value, + gas_price, + data: data.into(), + }; + + (next, data) + } + }; + + tx_datas.push(data); + data_ref = &data_ref[next..]; + } + + (tx_datas, data_ref) +} + +/// The transaction type - Legacy, EIP-2930, or EIP-1559 +#[derive(Debug)] +enum TxData { + /// A legacy transaction type + Legacy { + /// Transaction value + value: U256, + /// Transaction gas price + gas_price: U256, + /// Transaction calldata + data: Bytes, + }, + /// An EIP-2930 transaction type + Type1 { + /// Transaction value + value: U256, + /// Transaction gas price + gas_price: U256, + /// Transaction calldata + data: Bytes, + /// Access list as specified in EIP-2930 + access_list: AccessList, + }, + /// An EIP-1559 transaction type + Type2 { + /// Transaction value + value: U256, + /// Max fee per gas as specified in EIP-1559 + max_fee: U256, + /// Max priority fee as specified in EIP-1559 + max_priority_fee: U256, + /// Transaction calldata + data: Bytes, + /// Access list as specified in EIP-2930 + access_list: AccessList, + }, +} + +/// Decodes transaction `To` fields in the batch into an [Address] vector +fn decode_tos(data: &[u8], count: u64) -> (Vec
, &[u8]) { + let mut data_ref = data; + let mut tos = Vec::new(); + for _ in 0..count { + let (addr, d) = decode_address(data_ref); + tos.push(addr); + data_ref = d; + } + + (tos, data_ref) +} + +/// Decodes arbitrary slice of bytes into an [Address] +fn decode_address(data: &[u8]) -> (Address, &[u8]) { + let (address_bytes, data) = take_data(data, 20); + let address = Address::from_slice(address_bytes); + (address, data) +} + +/// Decodes transaction `R` & `S` signature fields in the batch into a (U256, U256) vector +fn decode_signatures(data: &[u8], tx_count: u64) -> (Vec<(U256, U256)>, &[u8]) { + let mut sigs = Vec::new(); + let mut data_ref = data; + for _ in 0..tx_count { + let (r, d) = decode_u256(data_ref); + data_ref = d; + + let (s, d) = decode_u256(data_ref); + data_ref = d; + + sigs.push((r, s)); + } + + (sigs, data_ref) +} + +/// Decodes a U256 from an arbitrary slice of bytes +fn decode_u256(data: &[u8]) -> (U256, &[u8]) { + let (bytes, data) = take_data(data, 32); + let value = U256::from_be_slice(bytes); + (value, data) +}